-
Notifications
You must be signed in to change notification settings - Fork 4k
xds: Implementation of Unified Matcher #12640
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
Merged
shivaspeaks
merged 30 commits into
grpc:master
from
shivaspeaks:xds-unified-matcher-and-cel
Jul 1, 2026
Merged
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
3bcdf09
xds: Implementation of Unified Matcher and CEL Integration
shivaspeaks 01c8509
add some unit tests to increase coverage
shivaspeaks 0e9f362
add some more unit tests
shivaspeaks 6be787a
add some more unit tests
shivaspeaks 9267ee4
fix/add tests
shivaspeaks 5b423ed
remove not required tests
shivaspeaks 2f53a30
add tests
shivaspeaks 5c433ce
add tests
shivaspeaks 568bec1
add tests
shivaspeaks 719b90c
add some tests
shivaspeaks 6da8f60
Address comments
shivaspeaks 4d6379d
remove dependency from dev.cel:cel
shivaspeaks a5f70b1
Refactor StringMatcher parsing logic
shivaspeaks 91de9cd
address comments and create registries
shivaspeaks 23a00d6
address comments
shivaspeaks f3042e2
add unit tests for exactMatchMap case
shivaspeaks 2f68101
add unit tests
shivaspeaks 0518230
Address Ashesh's comments
shivaspeaks 3f7fc86
use switch in PredicateEvaluator
shivaspeaks 7a6ca94
Address comments on CelStringExtractor
shivaspeaks 5836446
all overloaded methods should be placed together
shivaspeaks f312c9c
Restore MatchContext and CelMatcherTestHelper additions for Unified M…
shivaspeaks 0af8751
Remove cel.compiler dependency from interop-testing to fix CI
shivaspeaks d86e0aa
Cache Metadata.Key and BaseEncoding in HeaderMatchInput
shivaspeaks 89b2617
review comments
shivaspeaks 0bbc9e3
Address comments
shivaspeaks 81065a9
rework addressing comments
shivaspeaks addcfea
resolve comments and add tests
shivaspeaks 5e02211
address nits
shivaspeaks a31d8d2
add more uncovered branches in tests
shivaspeaks File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
71 changes: 71 additions & 0 deletions
71
xds/src/main/java/io/grpc/xds/internal/matcher/CelMatcher.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| /* | ||
| * Copyright 2026 The gRPC Authors | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package io.grpc.xds.internal.matcher; | ||
|
|
||
| import dev.cel.common.CelAbstractSyntaxTree; | ||
| import dev.cel.common.types.SimpleType; | ||
| import dev.cel.runtime.CelEvaluationException; | ||
| import dev.cel.runtime.CelRuntime; | ||
| import dev.cel.runtime.CelVariableResolver; | ||
|
|
||
| /** | ||
| * Executes compiled CEL expressions. | ||
| */ | ||
| final class CelMatcher { | ||
| private final CelRuntime.Program program; | ||
|
|
||
| private CelMatcher(CelRuntime.Program program) { | ||
| this.program = program; | ||
| } | ||
|
|
||
| /** | ||
| * Compiles the AST into a CelMatcher. | ||
| * Throws an Exception if evaluation fails during compilation setup. | ||
| */ | ||
| static CelMatcher compile(CelAbstractSyntaxTree ast) | ||
| throws CelEvaluationException { | ||
| // CelEvaluationException -> inside cel-runtime -> Allowed in production signatures | ||
| // CelValidationException -> inside cel-compiler -> Forbidden in production signatures | ||
| if (ast.getResultType() != SimpleType.BOOL) { | ||
| throw new IllegalArgumentException( | ||
| "CEL expression must evaluate to boolean, got: " + ast.getResultType()); | ||
| } | ||
| CelCommon.checkAllowedReferences(ast); | ||
| CelRuntime.Program program = CelCommon.RUNTIME.createProgram(ast); | ||
| return new CelMatcher(program); | ||
| } | ||
|
|
||
| /** | ||
| * Evaluates the CEL expression against the input activation. | ||
| */ | ||
| boolean match(Object input) throws CelEvaluationException { | ||
| Object result; | ||
| if (input instanceof CelVariableResolver) { | ||
| result = program.eval((CelVariableResolver) input); | ||
| } else { | ||
| throw new CelEvaluationException( | ||
| "Unsupported input type for CEL evaluation: " | ||
| + (input == null ? "null" : input.getClass().getName())); | ||
| } | ||
|
|
||
| if (result instanceof Boolean) { | ||
| return (Boolean) result; | ||
| } | ||
| throw new CelEvaluationException( | ||
| "CEL expression must evaluate to boolean, got: " + result.getClass().getName()); | ||
| } | ||
| } |
79 changes: 79 additions & 0 deletions
79
xds/src/main/java/io/grpc/xds/internal/matcher/CelStateMatcher.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| /* | ||
| * Copyright 2026 The gRPC Authors | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package io.grpc.xds.internal.matcher; | ||
|
|
||
| import com.github.xds.core.v3.TypedExtensionConfig; | ||
| import com.github.xds.type.v3.CelExpression; | ||
| import dev.cel.common.CelAbstractSyntaxTree; | ||
| import dev.cel.common.CelProtoAbstractSyntaxTree; | ||
| import dev.cel.runtime.CelEvaluationException; | ||
|
|
||
| /** | ||
| * Matcher for CEL expressions handling xDS CEL Matcher extension. | ||
| */ | ||
| final class CelStateMatcher implements Matcher { | ||
| private final CelMatcher compiledEndpoint; | ||
| static final String TYPE_URL = "type.googleapis.com/xds.type.matcher.v3.CelMatcher"; | ||
|
|
||
| CelStateMatcher(CelMatcher compiledEndpoint) { | ||
| this.compiledEndpoint = compiledEndpoint; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean match(Object value) { | ||
| try { | ||
| return compiledEndpoint.match(value); | ||
| } catch (CelEvaluationException e) { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public Class<?> inputType() { | ||
| return GrpcCelEnvironment.class; | ||
| } | ||
|
|
||
| static final class Provider implements MatcherProvider { | ||
| @Override | ||
| public CelStateMatcher getMatcher(TypedExtensionConfig config) { | ||
| try { | ||
| com.github.xds.type.matcher.v3.CelMatcher celProto = config.getTypedConfig() | ||
|
shivaspeaks marked this conversation as resolved.
|
||
| .unpack(com.github.xds.type.matcher.v3.CelMatcher.class); | ||
| if (!celProto.hasExprMatch()) { | ||
| throw new IllegalArgumentException("CelMatcher must have expr_match"); | ||
| } | ||
| CelExpression expr = celProto.getExprMatch(); | ||
| if (!expr.hasCelExprChecked()) { | ||
| throw new IllegalArgumentException("CelMatcher must have cel_expr_checked"); | ||
| } | ||
| CelAbstractSyntaxTree ast = | ||
| CelProtoAbstractSyntaxTree.fromCheckedExpr( | ||
| expr.getCelExprChecked()).getAst(); | ||
| CelMatcher compiled = CelMatcher.compile(ast); | ||
|
|
||
| return new CelStateMatcher(compiled); | ||
| } catch (Exception e) { | ||
| throw new IllegalArgumentException("Invalid CelMatcher config", e); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public String typeUrl() { | ||
| return TYPE_URL; | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
114 changes: 114 additions & 0 deletions
114
xds/src/main/java/io/grpc/xds/internal/matcher/HeaderMatchInput.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| /* | ||
| * Copyright 2026 The gRPC Authors | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package io.grpc.xds.internal.matcher; | ||
|
|
||
| import static com.google.common.base.Preconditions.checkNotNull; | ||
|
|
||
| import com.github.xds.core.v3.TypedExtensionConfig; | ||
| import com.google.common.io.BaseEncoding; | ||
| import com.google.protobuf.InvalidProtocolBufferException; | ||
| import io.envoyproxy.envoy.type.matcher.v3.HttpRequestHeaderMatchInput; | ||
| import io.grpc.Metadata; | ||
| import java.util.Locale; | ||
|
|
||
| /** | ||
| * MatchInput for extracting HTTP headers. | ||
| */ | ||
| final class HeaderMatchInput implements MatchInput { | ||
| private static final BaseEncoding BASE64 = BaseEncoding.base64(); | ||
| private final String headerName; | ||
| private final Metadata.Key<byte[]> binaryKey; | ||
| private final Metadata.Key<String> stringKey; | ||
|
|
||
| static final String TYPE_URL = | ||
| "type.googleapis.com/envoy.type.matcher.v3.HttpRequestHeaderMatchInput"; | ||
|
|
||
| HeaderMatchInput(String headerName) { | ||
| this.headerName = checkNotNull(headerName, "headerName"); | ||
| if (headerName.isEmpty() || headerName.length() >= 16384) { | ||
| throw new IllegalArgumentException( | ||
| "Header name length must be in range [1, 16384): " + headerName.length()); | ||
| } | ||
| if (!headerName.equals(headerName.toLowerCase(Locale.ROOT))) { | ||
| throw new IllegalArgumentException("Header name must be lowercase: " + headerName); | ||
| } | ||
| try { | ||
| if (headerName.endsWith(Metadata.BINARY_HEADER_SUFFIX)) { | ||
| this.binaryKey = Metadata.Key.of(headerName, Metadata.BINARY_BYTE_MARSHALLER); | ||
| this.stringKey = null; | ||
| } else { | ||
| this.binaryKey = null; | ||
| this.stringKey = Metadata.Key.of(headerName, Metadata.ASCII_STRING_MARSHALLER); | ||
| } | ||
| } catch (IllegalArgumentException e) { | ||
| throw new IllegalArgumentException("Invalid header name: " + headerName, e); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public String apply(MatchContext context) { | ||
| if ("te".equals(headerName)) { | ||
| return null; | ||
| } | ||
| if (binaryKey != null) { | ||
| Iterable<byte[]> values = context.getMetadata().getAll(binaryKey); | ||
| if (values == null) { | ||
| return null; | ||
| } | ||
| StringBuilder sb = new StringBuilder(); | ||
| boolean first = true; | ||
| for (byte[] value : values) { | ||
| if (!first) { | ||
| sb.append(","); | ||
| } | ||
| first = false; | ||
| sb.append(BASE64.encode(value)); | ||
| } | ||
| return sb.toString(); | ||
| } | ||
| Metadata metadata = context.getMetadata(); | ||
| Iterable<String> values = metadata.getAll(stringKey); | ||
| if (values == null) { | ||
| return null; | ||
| } | ||
| return String.join(",", values); | ||
| } | ||
|
|
||
| @Override | ||
| public Class<?> outputType() { | ||
| return String.class; | ||
| } | ||
|
|
||
| static final class Provider implements MatchInputProvider { | ||
| @Override | ||
| public HeaderMatchInput getInput(TypedExtensionConfig config) { | ||
| try { | ||
| HttpRequestHeaderMatchInput proto = config.getTypedConfig() | ||
| .unpack(HttpRequestHeaderMatchInput.class); | ||
| return new HeaderMatchInput(proto.getHeaderName()); | ||
| } catch (InvalidProtocolBufferException e) { | ||
| throw new IllegalArgumentException( | ||
| "Invalid input config: " + config.getTypedConfig().getTypeUrl(), e); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public String typeUrl() { | ||
| return TYPE_URL; | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.