Skip to content

Commit ee6df42

Browse files
committed
Implement GrpcCelEnvironment and MetadataHelper
1 parent 928f44a commit ee6df42

File tree

5 files changed

+156
-16
lines changed

5 files changed

+156
-16
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2024 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.xds.internal;
18+
19+
import com.google.common.base.Strings;
20+
import com.google.common.collect.ImmutableMap;
21+
import com.google.common.io.BaseEncoding;
22+
import io.grpc.Metadata;
23+
import java.util.ArrayList;
24+
import java.util.List;
25+
import javax.annotation.Nullable;
26+
27+
28+
public class MetadataHelper {
29+
public static ImmutableMap<String, String> metadataToHeaders(Metadata metadata) {
30+
return metadata.keys().stream().collect(ImmutableMap.toImmutableMap(
31+
headerName -> headerName,
32+
headerName -> Strings.nullToEmpty(deserializeHeader(metadata, headerName))));
33+
}
34+
35+
@Nullable
36+
public static String deserializeHeader(Metadata metadata, String headerName) {
37+
if (headerName.endsWith(Metadata.BINARY_HEADER_SUFFIX)) {
38+
Metadata.Key<byte[]> key;
39+
try {
40+
key = Metadata.Key.of(headerName, Metadata.BINARY_BYTE_MARSHALLER);
41+
} catch (IllegalArgumentException e) {
42+
return null;
43+
}
44+
Iterable<byte[]> values = metadata.getAll(key);
45+
if (values == null) {
46+
return null;
47+
}
48+
List<String> encoded = new ArrayList<>();
49+
for (byte[] v : values) {
50+
encoded.add(BaseEncoding.base64().omitPadding().encode(v));
51+
}
52+
return String.join(",", encoded);
53+
}
54+
Metadata.Key<String> key;
55+
try {
56+
key = Metadata.Key.of(headerName, Metadata.ASCII_STRING_MARSHALLER);
57+
} catch (IllegalArgumentException e) {
58+
return null;
59+
}
60+
Iterable<String> values = metadata.getAll(key);
61+
return values == null ? null : String.join(",", values);
62+
}
63+
}

xds/src/main/java/io/grpc/xds/internal/matchers/CelMatcher.java

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,18 @@
1818

1919
import static com.google.common.base.Preconditions.checkNotNull;
2020

21-
import com.google.common.collect.ImmutableMap;
2221
import dev.cel.common.CelAbstractSyntaxTree;
2322
import dev.cel.runtime.CelEvaluationException;
24-
import dev.cel.runtime.CelRuntime;
25-
import dev.cel.runtime.CelRuntimeFactory;
2623
import java.util.function.Predicate;
2724

2825
/** Unified Matcher API: xds.type.matcher.v3.CelMatcher. */
2926
public class CelMatcher implements Predicate<HttpMatchInput> {
30-
private static final CelRuntime CEL_RUNTIME =
31-
CelRuntimeFactory.standardCelRuntimeBuilder().build();
3227

33-
private final CelRuntime.Program program;
28+
private final GrpcCelEnvironment program;
3429
private final String description;
3530

3631
private CelMatcher(CelAbstractSyntaxTree ast, String description) throws CelEvaluationException {
37-
this.program = CEL_RUNTIME.createProgram(checkNotNull(ast));
32+
this.program = new GrpcCelEnvironment(checkNotNull(ast));
3833
this.description = description != null ? description : "";
3934
}
4035

@@ -57,11 +52,6 @@ public boolean test(HttpMatchInput httpMatchInput) {
5752
// return false;
5853
// }
5954
// TODO(sergiitk): [IMPL] convert headers to cel args
60-
try {
61-
return (boolean) program.eval(ImmutableMap.of("my_var", "Hello World"));
62-
} catch (CelEvaluationException e) {
63-
// TODO(sergiitk): [IMPL] log cel error?
64-
return false;
65-
}
55+
return program.eval(httpMatchInput.serverCall(), httpMatchInput.headers());
6656
}
6757
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2024 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.xds.internal.matchers;
18+
19+
import com.google.common.base.Strings;
20+
import com.google.common.collect.ImmutableMap;
21+
import dev.cel.common.CelAbstractSyntaxTree;
22+
import dev.cel.common.CelOptions;
23+
import dev.cel.common.types.SimpleType;
24+
import dev.cel.runtime.CelEvaluationException;
25+
import dev.cel.runtime.CelRuntime;
26+
import dev.cel.runtime.CelRuntimeFactory;
27+
import io.grpc.Metadata;
28+
import io.grpc.ServerCall;
29+
import io.grpc.Status;
30+
import io.grpc.xds.internal.MetadataHelper;
31+
32+
/** Unified Matcher API: xds.type.matcher.v3.CelMatcher. */
33+
public class GrpcCelEnvironment {
34+
private static final CelRuntime CEL_RUNTIME = CelRuntimeFactory
35+
.standardCelRuntimeBuilder()
36+
.setOptions(CelOptions.current().comprehensionMaxIterations(0).build())
37+
.build();
38+
39+
private final CelRuntime.Program program;
40+
41+
GrpcCelEnvironment(CelAbstractSyntaxTree ast) throws CelEvaluationException {
42+
if (ast.getResultType() != SimpleType.BOOL) {
43+
throw new CelEvaluationException("Expected bool return type");
44+
}
45+
this.program = CEL_RUNTIME.createProgram(ast);
46+
}
47+
48+
public boolean eval(ServerCall<?, ?> serverCall, Metadata metadata) {
49+
ImmutableMap.Builder<String, Object> requestBuilder = ImmutableMap.<String, Object>builder()
50+
.put("method", "POST")
51+
.put("host", Strings.nullToEmpty(serverCall.getAuthority()))
52+
.put("path", "/" + serverCall.getMethodDescriptor().getFullMethodName())
53+
.put("headers", MetadataHelper.metadataToHeaders(metadata));
54+
// TODO(sergiitk): handle other pseudo-headers
55+
try {
56+
return (boolean) program.eval(ImmutableMap.of("request", requestBuilder.build()));
57+
} catch (CelEvaluationException e) {
58+
throw Status.fromThrowable(e).asRuntimeException();
59+
}
60+
}
61+
}

xds/src/main/java/io/grpc/xds/internal/matchers/HttpMatchInput.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@
1919

2020
import com.google.auto.value.AutoValue;
2121
import io.grpc.Metadata;
22+
import io.grpc.ServerCall;
2223

2324
@AutoValue
2425
public abstract class HttpMatchInput {
2526
public abstract Metadata headers();
27+
2628
// TODO(sergiitk): [IMPL] consider
27-
// public abstract ServerCall<?, ?> serverCall();
29+
public abstract ServerCall<?, ?> serverCall();
2830
}

xds/src/test/java/io/grpc/xds/internal/matchers/CelMatcherTest.java

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@
2525
import dev.cel.compiler.CelCompilerFactory;
2626
import dev.cel.runtime.CelEvaluationException;
2727
import io.grpc.Metadata;
28+
import io.grpc.MethodDescriptor;
29+
import io.grpc.MethodDescriptor.MethodType;
30+
import io.grpc.NoopServerCall;
31+
import io.grpc.ServerCall;
32+
import io.grpc.StringMarshaller;
2833
import org.junit.Before;
2934
import org.junit.Test;
3035
import org.junit.runner.RunWith;
@@ -35,9 +40,12 @@ public class CelMatcherTest {
3540
// Construct the compilation and runtime environments.
3641
// These instances are immutable and thus trivially thread-safe and amenable to caching.
3742
private static final CelCompiler CEL_COMPILER =
38-
CelCompilerFactory.standardCelCompilerBuilder().addVar("my_var", SimpleType.STRING).build();
43+
CelCompilerFactory.standardCelCompilerBuilder()
44+
.addVar("request", SimpleType.ANY)
45+
.setResultType(SimpleType.BOOL)
46+
.build();
3947
private static final CelValidationResult celProg1 =
40-
CEL_COMPILER.compile("type(my_var) == string");
48+
CEL_COMPILER.compile("request.method == \"POST\"");
4149

4250
CelAbstractSyntaxTree ast1;
4351
CelMatcher matcher1;
@@ -47,6 +55,22 @@ public class CelMatcherTest {
4755
public Metadata headers() {
4856
return new Metadata();
4957
}
58+
59+
@Override public ServerCall<?, ?> serverCall() {
60+
final MethodDescriptor<String, String> method =
61+
MethodDescriptor.<String, String>newBuilder()
62+
.setType(MethodType.UNKNOWN)
63+
.setFullMethodName("service/method")
64+
.setRequestMarshaller(new StringMarshaller())
65+
.setResponseMarshaller(new StringMarshaller())
66+
.build();
67+
return new NoopServerCall<String, String>() {
68+
@Override
69+
public MethodDescriptor<String, String> getMethodDescriptor() {
70+
return method;
71+
}
72+
};
73+
}
5074
};
5175

5276
@Before

0 commit comments

Comments
 (0)