-
Notifications
You must be signed in to change notification settings - Fork 4k
xds: Implementation of Unified Matcher and CEL Integration #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
base: master
Are you sure you want to change the base?
Changes from 19 commits
f472c63
99aeef1
db4d885
33eff21
e28c8f5
78895db
19989eb
118d7af
77b01b9
310677d
49031b4
5a31706
c62d193
0c2d771
91d6bea
22f65ca
5c04ac3
82d9a8b
7b50380
4b626ad
c5bf128
c0b2b1d
e07d612
020f9ff
f20c5f0
0bd57e9
3b6129f
7cad139
7688bd8
8c53456
b379a9e
60a9356
8515729
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| /* | ||
| * 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.CelOptions; | ||
| import dev.cel.runtime.CelRuntime; | ||
| import dev.cel.runtime.CelRuntimeFactory; | ||
|
|
||
| /** | ||
| * Shared utilities for CEL-based matchers and extractors. | ||
| */ | ||
| final class CelCommon { | ||
| private static final CelOptions CEL_OPTIONS = CelOptions.newBuilder() | ||
| .enableComprehension(false) | ||
| .maxRegexProgramSize(100) | ||
|
shivaspeaks marked this conversation as resolved.
|
||
| .build(); | ||
|
|
||
|
|
||
| private static final dev.cel.runtime.CelStandardFunctions FUNCTIONS = | ||
| dev.cel.runtime.CelStandardFunctions.newBuilder() | ||
| .filterFunctions((func, over) -> { | ||
| if (func == dev.cel.runtime.CelStandardFunctions.StandardFunction.STRING) { | ||
| return false; | ||
| } | ||
| if (func == dev.cel.runtime.CelStandardFunctions.StandardFunction.ADD) { | ||
| return !over.equals( | ||
| (Object) dev.cel.runtime.standard.AddOperator.AddOverload.ADD_STRING) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we needing to cast to Object? If you're working around ErrorProne, then something seems broken and casting to Object is just hiding it.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes it was an errorProne workaround.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ADD_STRING is a CelStandardOverload, which isn't a StandardOverload. It seems ErrorProne is correct; these will never be equal. You should comment cases like this. If this were a bug in ErrorProne we'd probably want to file a bug with ErrorProne, but even if not, the comment helps us know when it is no longer necessary. (We don't have to guess why it was done, so we can confirm if that is no longer the case.) It looks like that has been adjusted on cel-java master, as the function is now passed CelStandardOverload. @sergiitk, the interface change looks like a breaking API change. That's surprising since I thought we were using stable APIs. Is this PR using an API that it shouldn't be, was this a known change that just hadn't been released yet, or should we be concerned about the actual stability of the API? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a relatively new API that didn't exist when API stabilization doc was made (go/cel-api-stabilization). It's stable now (I'll update the doc when I get a chance). Reason why I suggested this API is that it follows the recommended pattern for how environment subsetting is supposed to be done across all CEL stacks. As for EP -- I'm surprised it's actually flagging this (internally, it doesn't). AddOperator enum implements CelStandardOverload, so they should equal. My only guess is that CelStandardOverload is a functional interface, so theoretically it's possible to provide a lambda that's not
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I see, this is the new setup flow. I didn't look where this FUNCTIONS constant was used. That's fine; I'm less surprised that changed. (And yes, it's good to add it to the doc.)
I don't think it'd get flagged on master either. It only looked to be a problem because we're still using 0.11.1; in 0.11.1 I agree with ErrorProne that the equals() will always return false. |
||
| && !over.equals( | ||
| (Object) dev.cel.runtime.standard.AddOperator.AddOverload.ADD_LIST); | ||
| } | ||
| return true; | ||
|
shivaspeaks marked this conversation as resolved.
|
||
| }) | ||
| .build(); | ||
|
|
||
| static final CelRuntime RUNTIME = CelRuntimeFactory.standardCelRuntimeBuilder() | ||
| .setStandardEnvironmentEnabled(false) | ||
| .setStandardFunctions(FUNCTIONS) | ||
| .setOptions(CEL_OPTIONS) | ||
| .build(); | ||
|
|
||
| private CelCommon() {} | ||
|
|
||
| static void checkAllowedVariables(CelAbstractSyntaxTree ast) { | ||
| for (java.util.Map.Entry<Long, dev.cel.common.ast.CelReference> entry : | ||
| ast.getReferenceMap().entrySet()) { | ||
| dev.cel.common.ast.CelReference ref = entry.getValue(); | ||
| // If overload_id is empty, it's a variable reference or type name. | ||
| // We only support "request". | ||
| if (!ref.value().isPresent() && ref.overloadIds().isEmpty()) { | ||
| if (!"request".equals(ref.name())) { | ||
|
shivaspeaks marked this conversation as resolved.
Outdated
|
||
| throw new IllegalArgumentException( | ||
| "CEL expression references unknown variable: " + ref.name()); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| /* | ||
| * 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; | ||
|
|
||
| /** | ||
| * Executes compiled CEL expressions. | ||
| */ | ||
| public 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 validation or evaluation fails during compilation setup. | ||
| */ | ||
| public static CelMatcher compile(CelAbstractSyntaxTree ast) | ||
| throws Exception { | ||
|
shivaspeaks marked this conversation as resolved.
Outdated
|
||
| // 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.checkAllowedVariables(ast); | ||
| CelRuntime.Program program = CelCommon.RUNTIME.createProgram(ast); | ||
| return new CelMatcher(program); | ||
| } | ||
|
|
||
| /** | ||
| * Evaluates the CEL expression against the input activation. | ||
| */ | ||
| public boolean match(Object input) throws CelEvaluationException { | ||
| Object result; | ||
| if (input instanceof dev.cel.runtime.CelVariableResolver) { | ||
| result = program.eval((dev.cel.runtime.CelVariableResolver) input); | ||
| } else if (input instanceof java.util.Map) { | ||
| @SuppressWarnings("unchecked") | ||
| java.util.Map<String, ?> mapInput = (java.util.Map<String, ?>) input; | ||
| result = program.eval(mapInput); | ||
| } else { | ||
| throw new CelEvaluationException( | ||
| "Unsupported input type for CEL evaluation: " + input.getClass().getName()); | ||
| } | ||
|
|
||
| if (result instanceof Boolean) { | ||
| return (Boolean) result; | ||
| } | ||
| throw new CelEvaluationException( | ||
| "CEL expression must evaluate to boolean, got: " + result.getClass().getName()); | ||
| } | ||
| } | ||
| 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; | ||
|
|
||
| /** | ||
| * Executes compiled CEL expressions that extract a string. | ||
| */ | ||
| public final class CelStringExtractor { | ||
| private final CelRuntime.Program program; | ||
|
|
||
| private CelStringExtractor(CelRuntime.Program program) { | ||
| this.program = program; | ||
| } | ||
|
|
||
| /** | ||
| * Compiles the AST into a CelStringExtractor. | ||
| * Throws an Exception if validation or evaluation fails during compilation setup. | ||
| */ | ||
| public static CelStringExtractor compile(CelAbstractSyntaxTree ast) | ||
| throws Exception { | ||
| if (ast.getResultType() != SimpleType.STRING && ast.getResultType() != SimpleType.DYN) { | ||
| throw new IllegalArgumentException( | ||
| "CEL expression must evaluate to string, got: " + ast.getResultType()); | ||
| } | ||
| CelCommon.checkAllowedVariables(ast); | ||
|
shivaspeaks marked this conversation as resolved.
Outdated
|
||
| CelRuntime.Program program = CelCommon.RUNTIME.createProgram(ast); | ||
| return new CelStringExtractor(program); | ||
| } | ||
|
|
||
| /** | ||
| * Evaluates the CEL expression against the input activation and returns the string result. | ||
| * Returns null if the result is not a string. | ||
| */ | ||
| public String extract(Object input) throws CelEvaluationException { | ||
| Object result; | ||
| if (input instanceof dev.cel.runtime.CelVariableResolver) { | ||
| result = program.eval((dev.cel.runtime.CelVariableResolver) input); | ||
| } else if (input instanceof java.util.Map) { | ||
| @SuppressWarnings("unchecked") | ||
| java.util.Map<String, ?> mapInput = (java.util.Map<String, ?>) input; | ||
| result = program.eval(mapInput); | ||
| } else { | ||
| throw new CelEvaluationException( | ||
| "Unsupported input type for CEL evaluation: " + input.getClass().getName()); | ||
| } | ||
|
|
||
| if (result instanceof String) { | ||
| return (String) result; | ||
| } | ||
| return null; | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.