Skip to content

Commit 4e82ce0

Browse files
committed
Add unit test for generic constraint and add some context on CEL library
Add unit test cases for generic constraint using CEL to ensure it works properly in the resolver. Add some context for the custom library for semver comparison in CEL. Signed-off-by: Vu Dinh <[email protected]>
1 parent 59d2d86 commit 4e82ce0

File tree

4 files changed

+159
-18
lines changed

4 files changed

+159
-18
lines changed

pkg/controller/registry/resolver/cache/predicates.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ func (ep *evaluatorPredicate) Test(entry *Entry) bool {
383383
return ok
384384
}
385385

386-
func EvaluatorPredicate(provider constraints.EvaluatorProvider, rule, message string) (Predicate, error) {
386+
func EvaluatorPredicate(provider constraints.EvaluatorProvider, rule string, message string) (Predicate, error) {
387387
eval, err := provider.Evaluator(rule)
388388
if err != nil {
389389
return nil, err

pkg/controller/registry/resolver/constraints/cel.go

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,23 @@ type celEvaluatorProvider struct {
4343
}
4444

4545
type celEvaluator struct {
46-
p cel.Program
46+
program cel.Program
4747
}
4848

49+
/*
50+
This section of code is for custom library for semver comparison in CEL
51+
The code is inspired by https://github.com/google/cel-go/blob/master/cel/cel_test.go#L46
52+
53+
The semver_compare is wrriten based on `Compare` function in https://github.com/blang/semver
54+
particularly in https://github.com/blang/semver/blob/master/semver.go#L125
55+
56+
Example:
57+
`semver_compare(v1, v2)` is equivalent of `v1.Compare(v2)` in blang/semver library
58+
59+
The result is `semver_compare` is an integer just like `Compare`. So, the CEL
60+
expression `semver_compare(v1, v2) == 0` is equivalent v1.Compare(v2) == 0. In
61+
the other words, it checks if v1 is equal to v2 in term of semver comparision.
62+
*/
4963
type semverLib struct{}
5064

5165
func (semverLib) CompileOptions() []cel.EnvOption {
@@ -69,8 +83,21 @@ func (semverLib) ProgramOptions() []cel.ProgramOption {
6983
}
7084
}
7185

86+
func semverCompare(val1, val2 ref.Val) ref.Val {
87+
v1, err := semver.ParseTolerant(fmt.Sprint(val1.Value()))
88+
if err != nil {
89+
return types.ValOrErr(val1, "unable to parse '%v' to semver format", val1.Value())
90+
}
91+
92+
v2, err := semver.ParseTolerant(fmt.Sprint(val2.Value()))
93+
if err != nil {
94+
return types.ValOrErr(val2, "unable to parse '%v' to semver format", val2.Value())
95+
}
96+
return types.Int(v1.Compare(v2))
97+
}
98+
7299
func (e celEvaluator) Evaluate(env map[string]interface{}) (bool, error) {
73-
result, _, err := e.p.Eval(env)
100+
result, _, err := e.program.Eval(env)
74101
if err != nil {
75102
return false, err
76103
}
@@ -92,22 +119,9 @@ func (e *celEvaluatorProvider) Evaluator(rule string) (Evaluator, error) {
92119
return nil, fmt.Errorf("cel expressions must have type Bool")
93120
}
94121

95-
p, err := e.env.Program(ast)
122+
prog, err := e.env.Program(ast)
96123
if err != nil {
97124
return nil, err
98125
}
99-
return celEvaluator{p: p}, nil
100-
}
101-
102-
func semverCompare(val1, val2 ref.Val) ref.Val {
103-
v1, err := semver.ParseTolerant(fmt.Sprint(val1.Value()))
104-
if err != nil {
105-
return types.ValOrErr(val1, "unable to parse '%v' to semver format", val1.Value())
106-
}
107-
108-
v2, err := semver.ParseTolerant(fmt.Sprint(val2.Value()))
109-
if err != nil {
110-
return types.ValOrErr(val2, "unable to parse '%v' to semver format", val2.Value())
111-
}
112-
return types.Int(v1.Compare(v2))
126+
return celEvaluator{program: prog}, nil
113127
}

pkg/controller/registry/resolver/resolver_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/operator-framework/api/pkg/operators/v1alpha1"
2222
listersv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1"
2323
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
24+
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/constraints"
2425
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/solver"
2526
"github.com/operator-framework/operator-registry/pkg/api"
2627
opregistry "github.com/operator-framework/operator-registry/pkg/registry"
@@ -2380,3 +2381,124 @@ func TestNewOperatorFromCSV(t *testing.T) {
23802381
})
23812382
}
23822383
}
2384+
2385+
func TestSolveOperators_GenericConstraint(t *testing.T) {
2386+
Provides1 := cache.APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}}
2387+
namespace := "olm"
2388+
catalog := cache.SourceKey{Name: "community", Namespace: namespace}
2389+
2390+
deps1 := []*api.Dependency{
2391+
{
2392+
Type: "olm.constraint",
2393+
Value: `{"message":"gvk-constraint",
2394+
"cel":{"rule":"properties.exists(p, p.type == 'olm.gvk' && p.value == {'group': 'g', 'version': 'v', 'kind': 'k'})"}}`,
2395+
},
2396+
}
2397+
deps2 := []*api.Dependency{
2398+
{
2399+
Type: "olm.constraint",
2400+
Value: `{"message":"gvk2-constraint",
2401+
"cel":{"rule":"properties.exists(p, p.type == 'olm.gvk' && p.value == {'group': 'g2', 'version': 'v', 'kind': 'k'})"}}`,
2402+
},
2403+
}
2404+
deps3 := []*api.Dependency{
2405+
{
2406+
Type: "olm.constraint",
2407+
Value: `{"message":"package-constraint",
2408+
"cel":{"rule":"properties.exists(p, p.type == 'olm.package' && p.value.packageName == 'packageB' && (semver_compare(p.value.version, '1.0.1') == 0))"}}`,
2409+
},
2410+
}
2411+
2412+
tests := []struct {
2413+
name string
2414+
isErr bool
2415+
subs []*v1alpha1.Subscription
2416+
catalog cache.Source
2417+
expected cache.OperatorSet
2418+
message string
2419+
}{
2420+
{
2421+
// generic constraint for satisfiable gvk dependency
2422+
name: "Generic Constraint/Satisfiable GVK Dependency",
2423+
isErr: false,
2424+
subs: []*v1alpha1.Subscription{
2425+
newSub(namespace, "packageA", "stable", catalog),
2426+
},
2427+
catalog: &cache.Snapshot{
2428+
Entries: []*cache.Entry{
2429+
genOperator("opA.v1.0.0", "1.0.0", "", "packageA", "stable", catalog.Name, catalog.Namespace, nil, nil, deps1, "", false),
2430+
genOperator("opB.v1.0.0", "1.0.0", "", "packageB", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "stable", false),
2431+
},
2432+
},
2433+
expected: cache.OperatorSet{
2434+
"opA.v1.0.0": genOperator("opA.v1.0.0", "1.0.0", "", "packageA", "stable", catalog.Name, catalog.Namespace, nil, nil, deps1, "", false),
2435+
"opB.v1.0.0": genOperator("opB.v1.0.0", "1.0.0", "", "packageB", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "stable", false),
2436+
},
2437+
},
2438+
{
2439+
// generic constraint for NotSatisfiable gvk dependency
2440+
name: "Generic Constraint/NotSatisfiable GVK Dependency",
2441+
isErr: true,
2442+
subs: []*v1alpha1.Subscription{
2443+
newSub(namespace, "packageA", "stable", catalog),
2444+
},
2445+
catalog: &cache.Snapshot{
2446+
Entries: []*cache.Entry{
2447+
genOperator("opA.v1.0.0", "1.0.0", "", "packageA", "stable", catalog.Name, catalog.Namespace, nil, nil, deps2, "", false),
2448+
genOperator("opB.v1.0.0", "1.0.0", "", "packageB", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "", false),
2449+
},
2450+
},
2451+
// unable to find satisfiable gvk dependency
2452+
// resolve into nothing
2453+
expected: cache.OperatorSet{},
2454+
message: "gvk2-constraint",
2455+
},
2456+
{
2457+
// generic constraint for package constraint
2458+
name: "Generic Constraint/Satisfiable Package Dependency",
2459+
isErr: false,
2460+
subs: []*v1alpha1.Subscription{
2461+
newSub(namespace, "packageA", "stable", catalog),
2462+
},
2463+
catalog: &cache.Snapshot{
2464+
Entries: []*cache.Entry{
2465+
genOperator("opA.v1.0.0", "1.0.0", "", "packageA", "stable", catalog.Name, catalog.Namespace, nil, nil, deps3, "", false),
2466+
genOperator("opB.v1.0.0", "1.0.0", "", "packageB", "stable", catalog.Name, catalog.Namespace, nil, nil, nil, "", false),
2467+
genOperator("opB.v1.0.1", "1.0.1", "opB.v1.0.0", "packageB", "stable", catalog.Name, catalog.Namespace, nil, nil, nil, "stable", false),
2468+
genOperator("opB.v1.0.2", "1.0.2", "opB.v1.0.1", "packageB", "stable", catalog.Name, catalog.Namespace, nil, nil, nil, "stable", false),
2469+
},
2470+
},
2471+
expected: cache.OperatorSet{
2472+
"opA.v1.0.0": genOperator("opA.v1.0.1", "1.0.1", "", "packageA", "stable", catalog.Name, catalog.Namespace, nil, nil, deps3, "", false),
2473+
"opB.v1.0.1": genOperator("opB.v1.0.1", "1.0.1", "opB.v1.0.0", "packageB", "stable", catalog.Name, catalog.Namespace, nil, nil, nil, "stable", false),
2474+
},
2475+
},
2476+
}
2477+
2478+
for _, tt := range tests {
2479+
t.Run(tt.name, func(t *testing.T) {
2480+
var err error
2481+
var operators cache.OperatorSet
2482+
satResolver := SatResolver{
2483+
cache: cache.New(cache.StaticSourceProvider{
2484+
catalog: tt.catalog,
2485+
}),
2486+
log: logrus.New(),
2487+
evaluatorProvider: constraints.NewCelEvaluatorProvider(),
2488+
}
2489+
2490+
operators, err = satResolver.SolveOperators([]string{namespace}, nil, tt.subs)
2491+
if tt.isErr {
2492+
assert.Error(t, err)
2493+
assert.Contains(t, err.Error(), tt.message)
2494+
} else {
2495+
assert.NoError(t, err)
2496+
for k := range tt.expected {
2497+
require.NotNil(t, operators[k])
2498+
assert.EqualValues(t, k, operators[k].Name)
2499+
}
2500+
}
2501+
assert.Equal(t, len(tt.expected), len(operators))
2502+
})
2503+
}
2504+
}

pkg/controller/registry/resolver/source_registry.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,11 @@ func legacyDependenciesToProperties(dependencies []*api.Dependency) ([]*api.Prop
233233
Type: "olm.label.required",
234234
Value: dependency.Value,
235235
})
236+
case "olm.constraint":
237+
result = append(result, &api.Property{
238+
Type: "olm.constraint",
239+
Value: dependency.Value,
240+
})
236241
}
237242
}
238243
return result, nil

0 commit comments

Comments
 (0)