Add set ext library into Kubernetes and pick up the new option cel provides

This commit is contained in:
Cici Huang 2023-10-17 20:27:55 +00:00
parent 70c1f2143f
commit 8d804078f9
3 changed files with 267 additions and 2 deletions

View File

@ -230,8 +230,8 @@ func TestCelCostStability(t *testing.T) {
},
},
{name: "listSets",
obj: objs([]interface{}{"a", "b", "c"}, []interface{}{"a", "c", "b"}),
schema: schemas(listSetType(&stringType), listSetType(&stringType)),
obj: objs([]interface{}{"a", "b", "c"}, []interface{}{"a", "c", "b"}, buildLargeArray(1000)),
schema: schemas(listSetType(&stringType), listSetType(&stringType), listSetType(&integerType)),
expectCost: map[string]int64{
// equal even though order is different
"self.val1 == ['c', 'b', 'a']": 3,
@ -241,6 +241,12 @@ func TestCelCostStability(t *testing.T) {
"!('x' in self.val1)": 6,
"self.val1 + self.val2 == ['a', 'b', 'c']": 6,
"self.val1 + ['c', 'd'] == ['a', 'b', 'c', 'd']": 4,
"sets.contains(self.val1, ['a'])": 6,
"sets.equivalent(self.val1, ['a', 'b', 'c'])": 21,
"sets.intersects(self.val1, ['a'])": 6,
"sets.contains(self.val3, [1])": 1003,
"!sets.equivalent(self.val3, [1, 2, 3])": 6004,
"sets.intersects(self.val3, [1])": 1003,
},
},
{name: "listMaps",
@ -1157,6 +1163,14 @@ func TestCelCostStability(t *testing.T) {
}
}
func buildLargeArray(size int) []interface{} {
lArray := make([]interface{}, size)
for i := 0; i < len(lArray); i++ {
lArray[i] = i
}
return lArray
}
func TestCelEstimatedCostStability(t *testing.T) {
cases := []struct {
name string

View File

@ -22,7 +22,9 @@ import (
"sync"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/checker"
"github.com/google/cel-go/ext"
"github.com/google/cel-go/interpreter"
"golang.org/x/sync/singleflight"
"k8s.io/apimachinery/pkg/util/version"
@ -106,6 +108,21 @@ var baseOpts = []VersionedOptions{
ext.Strings(ext.StringsVersion(2)),
},
},
// Set library
{
IntroducedVersion: version.MajorMinor(1, 29),
EnvOptions: []cel.EnvOption{
ext.Sets(),
// cel-go v0.17.7 introduced CostEstimatorOptions.
// Previous the presence has a cost of 0 but cel fixed it to 1. We still set to 0 here to avoid breaking changes.
cel.CostEstimatorOptions(checker.PresenceTestHasCost(false)),
},
ProgramOptions: []cel.ProgramOption{
// cel-go v0.17.7 introduced CostTrackerOptions.
// Previous the presence has a cost of 0 but cel fixed it to 1. We still set to 0 here to avoid breaking changes.
cel.CostTrackerOptions(interpreter.PresenceTestHasCost(false)),
},
},
}
// MustBaseEnvSet returns the common CEL base environments for Kubernetes for Version, or panics

View File

@ -527,6 +527,236 @@ func TestQuantityCost(t *testing.T) {
}
}
func TestSetsCost(t *testing.T) {
cases := []struct {
name string
expr string
expectEstimatedCost checker.CostEstimate
expectRuntimeCost uint64
}{
{
name: "sets",
expr: `sets.contains([], [])`,
expectEstimatedCost: checker.CostEstimate{Min: 21, Max: 21},
expectRuntimeCost: 21,
},
{
expr: `sets.contains([1], [])`,
expectEstimatedCost: checker.CostEstimate{Min: 21, Max: 21},
expectRuntimeCost: 21,
},
{
expr: `sets.contains([1], [1])`,
expectEstimatedCost: checker.CostEstimate{Min: 22, Max: 22},
expectRuntimeCost: 22,
},
{
expr: `sets.contains([1], [1, 1])`,
expectEstimatedCost: checker.CostEstimate{Min: 23, Max: 23},
expectRuntimeCost: 23,
},
{
expr: `sets.contains([1, 1], [1])`,
expectEstimatedCost: checker.CostEstimate{Min: 23, Max: 23},
expectRuntimeCost: 23,
},
{
expr: `sets.contains([2, 1], [1])`,
expectEstimatedCost: checker.CostEstimate{Min: 23, Max: 23},
expectRuntimeCost: 23,
},
{
expr: `sets.contains([1, 2, 3, 4], [2, 3])`,
expectEstimatedCost: checker.CostEstimate{Min: 29, Max: 29},
expectRuntimeCost: 29,
},
{
expr: `sets.contains([1], [1.0, 1])`,
expectEstimatedCost: checker.CostEstimate{Min: 23, Max: 23},
expectRuntimeCost: 23,
},
{
expr: `sets.contains([1, 2], [2u, 2.0])`,
expectEstimatedCost: checker.CostEstimate{Min: 25, Max: 25},
expectRuntimeCost: 25,
},
{
expr: `sets.contains([1, 2u], [2, 2.0])`,
expectEstimatedCost: checker.CostEstimate{Min: 25, Max: 25},
expectRuntimeCost: 25,
},
{
expr: `sets.contains([1, 2.0, 3u], [1.0, 2u, 3])`,
expectEstimatedCost: checker.CostEstimate{Min: 30, Max: 30},
expectRuntimeCost: 30,
},
{
expr: `sets.contains([[1], [2, 3]], [[2, 3.0]])`,
// 10 for each list creation, top-level list sizes are 2, 1
expectEstimatedCost: checker.CostEstimate{Min: 53, Max: 53},
expectRuntimeCost: 53,
},
{
expr: `!sets.contains([1], [2])`,
expectEstimatedCost: checker.CostEstimate{Min: 23, Max: 23},
expectRuntimeCost: 23,
},
{
expr: `!sets.contains([1], [1, 2])`,
expectEstimatedCost: checker.CostEstimate{Min: 24, Max: 24},
expectRuntimeCost: 24,
},
{
expr: `!sets.contains([1], ["1", 1])`,
expectEstimatedCost: checker.CostEstimate{Min: 24, Max: 24},
expectRuntimeCost: 24,
},
{
expr: `!sets.contains([1], [1.1, 1u])`,
expectEstimatedCost: checker.CostEstimate{Min: 24, Max: 24},
expectRuntimeCost: 24,
},
// set equivalence (note the cost factor is higher as it's basically two contains checks)
{
expr: `sets.equivalent([], [])`,
expectEstimatedCost: checker.CostEstimate{Min: 21, Max: 21},
expectRuntimeCost: 21,
},
{
expr: `sets.equivalent([1], [1])`,
expectEstimatedCost: checker.CostEstimate{Min: 23, Max: 23},
expectRuntimeCost: 23,
},
{
expr: `sets.equivalent([1], [1, 1])`,
expectEstimatedCost: checker.CostEstimate{Min: 25, Max: 25},
expectRuntimeCost: 25,
},
{
expr: `sets.equivalent([1, 1], [1])`,
expectEstimatedCost: checker.CostEstimate{Min: 25, Max: 25},
expectRuntimeCost: 25,
},
{
expr: `sets.equivalent([1], [1u, 1.0])`,
expectEstimatedCost: checker.CostEstimate{Min: 25, Max: 25},
expectRuntimeCost: 25,
},
{
expr: `sets.equivalent([1], [1u, 1.0])`,
expectEstimatedCost: checker.CostEstimate{Min: 25, Max: 25},
expectRuntimeCost: 25,
},
{
expr: `sets.equivalent([1, 2, 3], [3u, 2.0, 1])`,
expectEstimatedCost: checker.CostEstimate{Min: 39, Max: 39},
expectRuntimeCost: 39,
},
{
expr: `sets.equivalent([[1.0], [2, 3]], [[1], [2, 3.0]])`,
expectEstimatedCost: checker.CostEstimate{Min: 69, Max: 69},
expectRuntimeCost: 69,
},
{
expr: `!sets.equivalent([2, 1], [1])`,
expectEstimatedCost: checker.CostEstimate{Min: 26, Max: 26},
expectRuntimeCost: 26,
},
{
expr: `!sets.equivalent([1], [1, 2])`,
expectEstimatedCost: checker.CostEstimate{Min: 26, Max: 26},
expectRuntimeCost: 26,
},
{
expr: `!sets.equivalent([1, 2], [2u, 2, 2.0])`,
expectEstimatedCost: checker.CostEstimate{Min: 34, Max: 34},
expectRuntimeCost: 34,
},
{
expr: `!sets.equivalent([1, 2], [1u, 2, 2.3])`,
expectEstimatedCost: checker.CostEstimate{Min: 34, Max: 34},
expectRuntimeCost: 34,
},
{
expr: `sets.intersects([1], [1])`,
expectEstimatedCost: checker.CostEstimate{Min: 22, Max: 22},
expectRuntimeCost: 22,
},
{
expr: `sets.intersects([1], [1, 1])`,
expectEstimatedCost: checker.CostEstimate{Min: 23, Max: 23},
expectRuntimeCost: 23,
},
{
expr: `sets.intersects([1, 1], [1])`,
expectEstimatedCost: checker.CostEstimate{Min: 23, Max: 23},
expectRuntimeCost: 23,
},
{
expr: `sets.intersects([2, 1], [1])`,
expectEstimatedCost: checker.CostEstimate{Min: 23, Max: 23},
expectRuntimeCost: 23,
},
{
expr: `sets.intersects([1], [1, 2])`,
expectEstimatedCost: checker.CostEstimate{Min: 23, Max: 23},
expectRuntimeCost: 23,
},
{
expr: `sets.intersects([1], [1.0, 2])`,
expectEstimatedCost: checker.CostEstimate{Min: 23, Max: 23},
expectRuntimeCost: 23,
},
{
expr: `sets.intersects([1, 2], [2u, 2, 2.0])`,
expectEstimatedCost: checker.CostEstimate{Min: 27, Max: 27},
expectRuntimeCost: 27,
},
{
expr: `sets.intersects([1, 2], [1u, 2, 2.3])`,
expectEstimatedCost: checker.CostEstimate{Min: 27, Max: 27},
expectRuntimeCost: 27,
},
{
expr: `sets.intersects([[1], [2, 3]], [[1, 2], [2, 3.0]])`,
expectEstimatedCost: checker.CostEstimate{Min: 65, Max: 65},
expectRuntimeCost: 65,
},
{
expr: `!sets.intersects([], [])`,
expectEstimatedCost: checker.CostEstimate{Min: 22, Max: 22},
expectRuntimeCost: 22,
},
{
expr: `!sets.intersects([1], [])`,
expectEstimatedCost: checker.CostEstimate{Min: 22, Max: 22},
expectRuntimeCost: 22,
},
{
expr: `!sets.intersects([1], [2])`,
expectEstimatedCost: checker.CostEstimate{Min: 23, Max: 23},
expectRuntimeCost: 23,
},
{
expr: `!sets.intersects([1], ["1", 2])`,
expectEstimatedCost: checker.CostEstimate{Min: 24, Max: 24},
expectRuntimeCost: 24,
},
{
expr: `!sets.intersects([1], [1.1, 2u])`,
expectEstimatedCost: checker.CostEstimate{Min: 24, Max: 24},
expectRuntimeCost: 24,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
testCost(t, tc.expr, tc.expectEstimatedCost, tc.expectRuntimeCost)
})
}
}
func testCost(t *testing.T, expr string, expectEsimatedCost checker.CostEstimate, expectRuntimeCost uint64) {
est := &CostEstimator{SizeEstimator: &testCostEstimator{}}
env, err := cel.NewEnv(
@ -536,6 +766,10 @@ func testCost(t *testing.T, expr string, expectEsimatedCost checker.CostEstimate
Lists(),
Authz(),
Quantity(),
ext.Sets(),
// cel-go v0.17.7 introduced CostEstimatorOptions.
// Previous the presence has a cost of 0 but cel fixed it to 1. We still set to 0 here to avoid breaking changes.
cel.CostEstimatorOptions(checker.PresenceTestHasCost(false)),
)
if err != nil {
t.Fatalf("%v", err)