Skip to content

Commit 0a4119e

Browse files
committed
Add system constraint provider logic to resolver and add step resolver init hooks
Signed-off-by: Per G. da Silva <[email protected]>
1 parent 82fbb1a commit 0a4119e

File tree

6 files changed

+192
-8
lines changed

6 files changed

+192
-8
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package resolver
2+
3+
// StepResolverInitHook provides a way for the downstream
4+
// to modify the step resolver at creation time.
5+
// This is a bit of a hack to enable system constraints downstream
6+
// without affecting the upstream. We may want to clean this up when
7+
// either we have a more pluggable architecture; or system constraints
8+
// come to the upstream
9+
type StepResolverInitHook func(*OperatorStepResolver) error

pkg/controller/registry/resolver/resolver.go

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ type OperatorResolver interface {
2525
}
2626

2727
type SatResolver struct {
28-
cache cache.OperatorCacheProvider
29-
log logrus.FieldLogger
28+
cache cache.OperatorCacheProvider
29+
log logrus.FieldLogger
30+
systemConstraintsProvider solver.ConstraintProvider
3031
}
3132

3233
func NewDefaultSatResolver(rcp cache.SourceProvider, catsrcLister v1alpha1listers.CatalogSourceLister, logger logrus.FieldLogger) *SatResolver {
@@ -172,6 +173,23 @@ func (r *SatResolver) SolveOperators(namespaces []string, csvs []*v1alpha1.Clust
172173
return operators, nil
173174
}
174175

176+
func (r *SatResolver) toBundleInstallable(entry *cache.Entry) (*BundleInstallable, error) {
177+
bundleInstalleble, err := NewBundleInstallableFromOperator(entry)
178+
if err != nil {
179+
return nil, err
180+
}
181+
182+
// apply system constraints if necessary
183+
if r.systemConstraintsProvider != nil && !(entry.SourceInfo.Catalog.Virtual()) {
184+
systemConstraints, err := r.systemConstraintsProvider.Constraints(entry)
185+
if err != nil {
186+
return nil, err
187+
}
188+
bundleInstalleble.constraints = append(bundleInstalleble.constraints, systemConstraints...)
189+
}
190+
return &bundleInstalleble, nil
191+
}
192+
175193
func (r *SatResolver) getSubscriptionInstallables(sub *v1alpha1.Subscription, current *cache.Entry, namespacedCache cache.MultiCatalogOperatorFinder, visited map[*cache.Entry]*BundleInstallable) (map[solver.Identifier]solver.Installable, error) {
176194
var cachePredicates, channelPredicates []cache.Predicate
177195
installables := make(map[solver.Identifier]solver.Installable, 0)
@@ -329,13 +347,13 @@ func (r *SatResolver) getBundleInstallables(preferredNamespace string, bundleSta
329347
continue
330348
}
331349

332-
bundleInstallable, err := NewBundleInstallableFromOperator(bundle)
350+
bundleInstallable, err := r.toBundleInstallable(bundle)
333351
if err != nil {
334352
errs = append(errs, err)
335353
continue
336354
}
337355

338-
visited[bundle] = &bundleInstallable
356+
visited[bundle] = bundleInstallable
339357

340358
dependencyPredicates, err := DependencyPredicates(bundle.Properties)
341359
if err != nil {
@@ -384,12 +402,12 @@ func (r *SatResolver) getBundleInstallables(preferredNamespace string, bundleSta
384402
// (after sorting) to remove all bundles that
385403
// don't satisfy the dependency.
386404
for _, b := range cache.Filter(sortedBundles, d) {
387-
i, err := NewBundleInstallableFromOperator(b)
405+
i, err := r.toBundleInstallable(b)
388406
if err != nil {
389407
errs = append(errs, err)
390408
continue
391409
}
392-
installables[i.Identifier()] = &i
410+
installables[i.Identifier()] = i
393411
bundleDependencies = append(bundleDependencies, i.Identifier())
394412
bundleStack = append(bundleStack, b)
395413
}
@@ -399,7 +417,7 @@ func (r *SatResolver) getBundleInstallables(preferredNamespace string, bundleSta
399417
))
400418
}
401419

402-
installables[bundleInstallable.Identifier()] = &bundleInstallable
420+
installables[bundleInstallable.Identifier()] = bundleInstallable
403421
}
404422

405423
if len(errs) > 0 {

pkg/controller/registry/resolver/resolver_test.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,109 @@ func TestSolveOperators(t *testing.T) {
5757
require.EqualValues(t, expected, operators)
5858
}
5959

60+
func TestSolveOperators_WithSystemConstraints(t *testing.T) {
61+
const namespace = "test-namespace"
62+
catalog := cache.SourceKey{Name: "test-catalog", Namespace: namespace}
63+
64+
packageASub := newSub(namespace, "packageA", "alpha", catalog)
65+
packageDSub := existingSub(namespace, "packageD.v1", "packageD", "alpha", catalog)
66+
67+
APISet := cache.APISet{opregistry.APIKey{Group: "g", Version: "v", Kind: "k", Plural: "ks"}: struct{}{}}
68+
69+
// packageA requires an API that can be provided by B or C
70+
packageA := genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", catalog.Name, catalog.Namespace, APISet, nil, nil, "", false)
71+
packageB := genOperator("packageB.v1", "1.0.0", "", "packageB", "alpha", catalog.Name, catalog.Namespace, nil, APISet, nil, "", false)
72+
packageC := genOperator("packageC.v1", "1.0.0", "", "packageC", "alpha", catalog.Name, catalog.Namespace, nil, APISet, nil, "", false)
73+
74+
// Existing operators
75+
packageD := genOperator("packageD.v1", "1.0.0", "", "packageD", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false)
76+
existingPackageD := existingOperator(namespace, "packageD.v1", "packageD", "alpha", "", nil, nil, nil, nil)
77+
existingPackageD.Annotations = map[string]string{"operatorframework.io/properties": `{"properties":[{"type":"olm.package","value":{"packageName":"packageD","version":"1.0.0"}}]}`}
78+
79+
whiteListConstraintProvider := func(whiteList ...*cache.Entry) solver.ConstraintProviderFunc {
80+
return func(entry *cache.Entry) ([]solver.Constraint, error) {
81+
for _, whiteListedEntry := range whiteList {
82+
if whiteListedEntry.Package() == entry.Package() &&
83+
whiteListedEntry.Name == entry.Name &&
84+
whiteListedEntry.Version == entry.Version {
85+
return nil, nil
86+
}
87+
}
88+
return []solver.Constraint{PrettyConstraint(
89+
solver.Prohibited(),
90+
fmt.Sprintf("package: %s is not white listed", entry.Package()),
91+
)}, nil
92+
}
93+
}
94+
95+
testCases := []struct {
96+
title string
97+
systemConstraintsProvider solver.ConstraintProvider
98+
expectedOperators cache.OperatorSet
99+
csvs []*v1alpha1.ClusterServiceVersion
100+
subs []*v1alpha1.Subscription
101+
snapshotEntries []*cache.Entry
102+
err string
103+
}{
104+
{
105+
title: "No runtime constraints",
106+
snapshotEntries: []*cache.Entry{packageA, packageB, packageC, packageD},
107+
systemConstraintsProvider: nil,
108+
expectedOperators: cache.OperatorSet{"packageA.v1": packageA, "packageB.v1": packageB},
109+
csvs: nil,
110+
subs: []*v1alpha1.Subscription{packageASub},
111+
err: "",
112+
},
113+
{
114+
title: "Runtime constraints only accept packages A and C",
115+
snapshotEntries: []*cache.Entry{packageA, packageB, packageC, packageD},
116+
systemConstraintsProvider: whiteListConstraintProvider(packageA, packageC),
117+
expectedOperators: cache.OperatorSet{"packageA.v1": packageA, "packageC.v1": packageC},
118+
csvs: nil,
119+
subs: []*v1alpha1.Subscription{packageASub},
120+
err: "",
121+
},
122+
{
123+
title: "Existing packages are ignored",
124+
snapshotEntries: []*cache.Entry{packageA, packageB, packageC, packageD},
125+
systemConstraintsProvider: whiteListConstraintProvider(packageA, packageC),
126+
expectedOperators: cache.OperatorSet{"packageA.v1": packageA, "packageC.v1": packageC},
127+
csvs: []*v1alpha1.ClusterServiceVersion{existingPackageD},
128+
subs: []*v1alpha1.Subscription{packageASub, packageDSub},
129+
err: "",
130+
},
131+
{
132+
title: "Runtime constraints don't allow A",
133+
snapshotEntries: []*cache.Entry{packageA, packageB, packageC, packageD},
134+
systemConstraintsProvider: whiteListConstraintProvider(),
135+
expectedOperators: nil,
136+
csvs: nil,
137+
subs: []*v1alpha1.Subscription{packageASub},
138+
err: "packageA is not white listed",
139+
},
140+
}
141+
142+
for _, testCase := range testCases {
143+
satResolver := SatResolver{
144+
cache: cache.New(cache.StaticSourceProvider{
145+
catalog: &cache.Snapshot{
146+
Entries: testCase.snapshotEntries,
147+
},
148+
}),
149+
log: logrus.New(),
150+
systemConstraintsProvider: testCase.systemConstraintsProvider,
151+
}
152+
operators, err := satResolver.SolveOperators([]string{namespace}, testCase.csvs, testCase.subs)
153+
154+
if testCase.err != "" {
155+
require.Containsf(t, err.Error(), testCase.err, "Test %s failed", testCase.title)
156+
} else {
157+
require.NoErrorf(t, err, "Test %s failed", testCase.title)
158+
}
159+
require.EqualValuesf(t, testCase.expectedOperators, operators, "Test %s failed", testCase.title)
160+
}
161+
}
162+
60163
func TestDisjointChannelGraph(t *testing.T) {
61164
const namespace = "test-namespace"
62165
catalog := cache.SourceKey{Name: "test-catalog", Namespace: namespace}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package solver
2+
3+
import "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
4+
5+
// ConstraintProvider knows how to provide solver constraints for a given cache entry.
6+
type ConstraintProvider interface {
7+
// Constraints returns a set of solver constraints for a cache entry.
8+
Constraints(e *cache.Entry) ([]Constraint, error)
9+
}
10+
11+
type ConstraintProviderFunc func(e *cache.Entry) ([]Constraint, error)
12+
13+
func (c ConstraintProviderFunc) Constraints(e *cache.Entry) ([]Constraint, error) {
14+
return c(e)
15+
}

pkg/controller/registry/resolver/step_resolver.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ const (
2626
BundleLookupConditionPacked v1alpha1.BundleLookupConditionType = "BundleLookupNotPersisted"
2727
)
2828

29+
// init hooks provides the downstream a way to modify the upstream behavior
30+
var initHooks []StepResolverInitHook
31+
2932
var timeNow = func() metav1.Time { return metav1.NewTime(time.Now().UTC()) }
3033

3134
type StepResolver interface {
@@ -48,7 +51,7 @@ var _ StepResolver = &OperatorStepResolver{}
4851

4952
func NewOperatorStepResolver(lister operatorlister.OperatorLister, client versioned.Interface, kubeclient kubernetes.Interface,
5053
globalCatalogNamespace string, provider RegistryClientProvider, log logrus.FieldLogger) *OperatorStepResolver {
51-
return &OperatorStepResolver{
54+
stepResolver := &OperatorStepResolver{
5255
subLister: lister.OperatorsV1alpha1().SubscriptionLister(),
5356
csvLister: lister.OperatorsV1alpha1().ClusterServiceVersionLister(),
5457
ipLister: lister.OperatorsV1alpha1().InstallPlanLister(),
@@ -58,6 +61,15 @@ func NewOperatorStepResolver(lister operatorlister.OperatorLister, client versio
5861
satResolver: NewDefaultSatResolver(SourceProviderFromRegistryClientProvider(provider, log), lister.OperatorsV1alpha1().CatalogSourceLister(), log),
5962
log: log,
6063
}
64+
65+
// init hooks can be added to the downstream to
66+
// modify resolver behaviour
67+
for _, initHook := range initHooks {
68+
if err := initHook(stepResolver); err != nil {
69+
panic(err)
70+
}
71+
}
72+
return stepResolver
6173
}
6274

6375
func (r *OperatorStepResolver) Expire(key cache.SourceKey) {

pkg/controller/registry/resolver/step_resolver_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,33 @@ var (
4747
Requires4 = APISet4
4848
)
4949

50+
func TestInitHooks(t *testing.T) {
51+
clientFake := fake.NewSimpleClientset()
52+
lister := operatorlister.NewLister()
53+
kClientFake := k8sfake.NewSimpleClientset()
54+
log := logrus.New()
55+
56+
// no init hooks
57+
resolver := NewOperatorStepResolver(lister, clientFake, kClientFake, "", nil, log)
58+
require.NotNil(t, resolver.satResolver)
59+
60+
// with init hook
61+
var testHook StepResolverInitHook = func(resolver *OperatorStepResolver) error {
62+
resolver.satResolver = nil
63+
return nil
64+
}
65+
66+
// defined in step_resolver.go
67+
initHooks = append(initHooks, testHook)
68+
defer func() {
69+
// reset initHooks
70+
initHooks = nil
71+
}()
72+
73+
resolver = NewOperatorStepResolver(lister, clientFake, kClientFake, "", nil, log)
74+
require.Nil(t, resolver.satResolver)
75+
}
76+
5077
func TestResolver(t *testing.T) {
5178
const namespace = "catsrc-namespace"
5279
catalog := resolvercache.SourceKey{Name: "catsrc", Namespace: namespace}

0 commit comments

Comments
 (0)