Skip to content

Commit 3eaa726

Browse files
committed
Add second implementation option
Signed-off-by: Per G. da Silva <[email protected]>
1 parent ca038a8 commit 3eaa726

File tree

6 files changed

+237
-8
lines changed

6 files changed

+237
-8
lines changed

pkg/controller/registry/reconciler/reconciler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ func Pod(source *v1alpha1.CatalogSource, name string, image string, saName strin
122122
},
123123
Spec: v1.PodSpec{
124124
// TODO: Remove this before merging
125-
HostNetwork: true,
125+
// HostNetwork: true,
126126
Containers: []v1.Container{
127127
{
128128
Name: name,

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,24 @@ func (p orPredicate) String() string {
282282
return b.String()
283283
}
284284

285+
type notPredicate struct {
286+
predicate Predicate
287+
}
288+
289+
func Not(predicate Predicate) Predicate {
290+
return notPredicate{
291+
predicate: predicate,
292+
}
293+
}
294+
295+
func (p notPredicate) Test(o *Entry) bool {
296+
return !p.predicate.Test(o)
297+
}
298+
299+
func (p notPredicate) String() string {
300+
return fmt.Sprintf("not %s", p.predicate.String())
301+
}
302+
285303
type booleanPredicate struct {
286304
result bool
287305
}

pkg/controller/registry/resolver/installabletypes.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ func (i *BundleInstallable) AddConstraint(c solver.Constraint) {
3636
i.constraints = append(i.constraints, c)
3737
}
3838

39+
func (i *BundleInstallable) AddRuntimeConstraintFailure(message string) {
40+
msg := fmt.Sprintf("%s violates a cluster runtime constraint: %s", i.identifier, message)
41+
i.AddConstraint(PrettyConstraint(solver.Prohibited(), msg))
42+
}
43+
3944
func (i *BundleInstallable) BundleSourceInfo() (string, string, cache.SourceKey, error) {
4045
info := strings.Split(i.identifier.String(), "/")
4146
// This should be enforced by Kube naming constraints

pkg/controller/registry/resolver/resolver.go

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ package resolver
33
import (
44
"context"
55
"encoding/json"
6+
"errors"
67
"fmt"
8+
"os"
79
"sort"
810
"strings"
911

12+
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/runtime_constraints"
13+
1014
"github.com/blang/semver/v4"
1115
"github.com/sirupsen/logrus"
1216
utilerrors "k8s.io/apimachinery/pkg/util/errors"
@@ -20,25 +24,42 @@ import (
2024
opregistry "github.com/operator-framework/operator-registry/pkg/registry"
2125
)
2226

27+
const (
28+
runtimeConstraintsFilePath = "runtime_constraints.yaml"
29+
)
30+
2331
type OperatorResolver interface {
2432
SolveOperators(csvs []*v1alpha1.ClusterServiceVersion, subs []*v1alpha1.Subscription, add map[cache.OperatorSourceInfo]struct{}) (cache.OperatorSet, error)
2533
}
2634

2735
type SatResolver struct {
28-
cache cache.OperatorCacheProvider
29-
log logrus.FieldLogger
36+
cache cache.OperatorCacheProvider
37+
log logrus.FieldLogger
38+
runtimeConstraintsProvider *runtime_constraints.RuntimeConstraintsProvider
3039
}
3140

3241
func NewDefaultSatResolver(rcp cache.SourceProvider, catsrcLister v1alpha1listers.CatalogSourceLister, logger logrus.FieldLogger) *SatResolver {
33-
// Get runtime constraints somehow
34-
runtimeConstraints := []cache.Predicate{
35-
// Only etcd can be installed on this system!
36-
cache.PkgPredicate("etcd"),
42+
43+
runtimeConstraintProvider, err := runtime_constraints.NewFromFile(runtimeConstraintsFilePath)
44+
if err != nil && !errors.Is(err, os.ErrNotExist) {
45+
if errors.Is(err, os.ErrNotExist) {
46+
logger.Warning("No cluster runtime constraints file found")
47+
} else {
48+
logger.Errorf("Error creating runtime constraints from file: %s", err)
49+
panic(err)
50+
}
3751
}
3852

53+
// Two solutions:
54+
// #1: Global cache filters
55+
// globalFilters := cache.WithGlobalFilters(runtimeConstraintProvider.GetRuntimeConstraints()...)
56+
3957
return &SatResolver{
40-
cache: cache.New(rcp, cache.WithLogger(logger), cache.WithCatalogSourceLister(catsrcLister), cache.WithGlobalFilters(runtimeConstraints...)),
58+
cache: cache.New(rcp, cache.WithLogger(logger), cache.WithCatalogSourceLister(catsrcLister) /*, globalFilters*/),
4159
log: logger,
60+
// #2: apply constraints in addInvariants
61+
runtimeConstraintsProvider: runtimeConstraintProvider, // uncomment when using option #2
62+
// runtimeConstraintsProvider: nil // uncomment when using option #1
4263
}
4364
}
4465

@@ -574,6 +595,16 @@ func (r *SatResolver) addInvariants(namespacedCache cache.MultiCatalogOperatorFi
574595
}
575596
packageConflictToInstallable[prop.PackageName] = append(packageConflictToInstallable[prop.PackageName], installable.Identifier())
576597
}
598+
599+
// apply runtime constraints to packages that aren't already installed
600+
if !catalog.Virtual() && r.runtimeConstraintsProvider != nil {
601+
for _, predicate := range r.runtimeConstraintsProvider.GetRuntimeConstraints() {
602+
if !predicate.Test(op) {
603+
bundleInstallable.AddRuntimeConstraintFailure(predicate.String())
604+
break
605+
}
606+
}
607+
}
577608
}
578609

579610
for gvk, is := range gvkConflictToInstallable {

pkg/controller/registry/resolver/resolver_test.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"fmt"
66
"testing"
77

8+
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/runtime_constraints"
9+
810
"github.com/blang/semver/v4"
911
"github.com/sirupsen/logrus"
1012
"github.com/sirupsen/logrus/hooks/test"
@@ -83,6 +85,99 @@ func TestDisjointChannelGraph(t *testing.T) {
8385
require.Error(t, err, "a unique replacement chain within a channel is required to determine the relative order between channel entries, but 2 replacement chains were found in channel \"alpha\" of package \"packageA\": packageA.side1.v2...packageA.side1.v1, packageA.side2.v2...packageA.side2.v1")
8486
}
8587

88+
func TestRuntimeConstraints(t *testing.T) {
89+
const namespace = "test-namespace"
90+
catalog := cache.SourceKey{Name: "test-catalog", Namespace: namespace}
91+
92+
packageASub := newSub(namespace, "packageA", "alpha", catalog)
93+
packageDSub := existingSub(namespace, "packageD.v1", "packageD", "alpha", catalog)
94+
95+
APISet := cache.APISet{opregistry.APIKey{Group: "g", Version: "v", Kind: "k", Plural: "ks"}: struct{}{}}
96+
97+
// packageA requires an API that can be provided by B or C
98+
packageA := genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", catalog.Name, catalog.Namespace, APISet, nil, nil, "", false)
99+
packageB := genOperator("packageB.v1", "1.0.0", "", "packageB", "alpha", catalog.Name, catalog.Namespace, nil, APISet, nil, "", false)
100+
packageC := genOperator("packageC.v1", "1.0.0", "", "packageC", "alpha", catalog.Name, catalog.Namespace, nil, APISet, nil, "", false)
101+
packageD := genOperator("packageD.v1", "1.0.0", "", "packageD", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false)
102+
103+
// Existing operators
104+
existingPackageD := existingOperator(namespace, "packageD.v1", "packageD", "alpha", "", nil, nil, nil, nil)
105+
existingPackageD.Annotations = map[string]string{"operatorframework.io/properties": `{"properties":[{"type":"olm.package","value":{"packageName":"packageD","version":"1.0.0"}}]}`}
106+
107+
testCases := []struct {
108+
title string
109+
runtimeConstraints []cache.Predicate
110+
expectedOperators cache.OperatorSet
111+
csvs []*v1alpha1.ClusterServiceVersion
112+
subs []*v1alpha1.Subscription
113+
snapshotEntries []*cache.Entry
114+
err string
115+
}{
116+
{
117+
title: "No runtime constraints",
118+
snapshotEntries: []*cache.Entry{packageA, packageB, packageC, packageD},
119+
runtimeConstraints: []cache.Predicate{},
120+
expectedOperators: cache.OperatorSet{"packageA.v1": packageA, "packageB.v1": packageB},
121+
csvs: nil,
122+
subs: []*v1alpha1.Subscription{packageASub},
123+
err: "",
124+
},
125+
{
126+
title: "Runtime constraints only accept packages A and C",
127+
snapshotEntries: []*cache.Entry{packageA, packageB, packageC, packageD},
128+
runtimeConstraints: []cache.Predicate{
129+
cache.Or(cache.PkgPredicate("packageA"), cache.PkgPredicate("packageC")),
130+
},
131+
expectedOperators: cache.OperatorSet{"packageA.v1": packageA, "packageC.v1": packageC},
132+
csvs: nil,
133+
subs: []*v1alpha1.Subscription{packageASub},
134+
err: "",
135+
},
136+
{
137+
title: "Existing packages are ignored",
138+
snapshotEntries: []*cache.Entry{packageA, packageB, packageC, packageD},
139+
runtimeConstraints: []cache.Predicate{
140+
cache.Or(cache.PkgPredicate("packageA"), cache.PkgPredicate("packageC")),
141+
},
142+
expectedOperators: cache.OperatorSet{"packageA.v1": packageA, "packageC.v1": packageC},
143+
csvs: []*v1alpha1.ClusterServiceVersion{existingPackageD},
144+
subs: []*v1alpha1.Subscription{packageASub, packageDSub},
145+
err: "",
146+
},
147+
{
148+
title: "Runtime constraints don't allow A",
149+
snapshotEntries: []*cache.Entry{packageA, packageB, packageC, packageD},
150+
runtimeConstraints: []cache.Predicate{
151+
cache.Not(cache.PkgPredicate("packageA")),
152+
},
153+
expectedOperators: nil,
154+
csvs: nil,
155+
subs: []*v1alpha1.Subscription{packageASub},
156+
err: "test-catalog/test-namespace/alpha/packageA.v1 violates a cluster runtime constraint: not with package: packageA",
157+
},
158+
}
159+
160+
for _, testCase := range testCases {
161+
satResolver := SatResolver{
162+
cache: cache.New(cache.StaticSourceProvider{
163+
catalog: &cache.Snapshot{
164+
Entries: testCase.snapshotEntries,
165+
},
166+
}),
167+
log: logrus.New(),
168+
runtimeConstraintsProvider: runtime_constraints.New(testCase.runtimeConstraints),
169+
}
170+
operators, err := satResolver.SolveOperators([]string{namespace}, testCase.csvs, testCase.subs)
171+
172+
if testCase.err != "" {
173+
require.Containsf(t, err.Error(), testCase.err, "Test %s failed", testCase.title)
174+
} else {
175+
require.NoErrorf(t, err, "Test %s failed", testCase.title)
176+
}
177+
require.EqualValuesf(t, testCase.expectedOperators, operators, "Test %s failed", testCase.title)
178+
}
179+
}
180+
86181
func TestPropertiesAnnotationHonored(t *testing.T) {
87182
const (
88183
namespace = "olm"
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package runtime_constraints
2+
3+
import (
4+
"encoding/json"
5+
"io/ioutil"
6+
7+
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
8+
"github.com/operator-framework/operator-registry/pkg/registry"
9+
"github.com/pkg/errors"
10+
)
11+
12+
const (
13+
maxRuntimeConstraints = 10
14+
)
15+
16+
type RuntimeConstraintsProvider struct {
17+
runtimeConstraints []cache.Predicate
18+
}
19+
20+
func (p *RuntimeConstraintsProvider) GetRuntimeConstraints() []cache.Predicate {
21+
return p.runtimeConstraints
22+
}
23+
24+
func New(runtimeConstraints []cache.Predicate) *RuntimeConstraintsProvider {
25+
return &RuntimeConstraintsProvider{
26+
runtimeConstraints: runtimeConstraints,
27+
}
28+
}
29+
30+
func NewFromFile(runtimeConstraintsFilePath string) (*RuntimeConstraintsProvider, error) {
31+
propertiesFile, err := readRuntimeConstraintsYaml(runtimeConstraintsFilePath)
32+
if err != nil {
33+
return nil, err
34+
}
35+
36+
// Using package type to test with
37+
// We may only want to allow the generic constraint types once they are readym
38+
var constraints = make([]cache.Predicate, 0)
39+
for _, property := range propertiesFile.Properties {
40+
rawMessage := []byte(property.Value)
41+
switch property.Type {
42+
case registry.PackageType:
43+
dep := registry.PackageDependency{}
44+
err := json.Unmarshal(rawMessage, &dep)
45+
if err != nil {
46+
return nil, err
47+
}
48+
constraints = append(constraints, cache.PkgPredicate(dep.PackageName))
49+
case registry.LabelType:
50+
dep := registry.LabelDependency{}
51+
err := json.Unmarshal(rawMessage, &dep)
52+
if err != nil {
53+
return nil, err
54+
}
55+
constraints = append(constraints, cache.LabelPredicate(dep.Label))
56+
}
57+
if len(constraints) >= maxRuntimeConstraints {
58+
return nil, errors.Errorf("Too many runtime constraints defined (%d/%d)", len(constraints), maxRuntimeConstraints)
59+
}
60+
}
61+
62+
return New(constraints), nil
63+
}
64+
65+
func readRuntimeConstraintsYaml(yamlPath string) (*registry.PropertiesFile, error) {
66+
// Read file
67+
yamlFile, err := ioutil.ReadFile(yamlPath)
68+
if err != nil {
69+
return nil, err
70+
}
71+
72+
// Parse yaml
73+
var propertiesFile = &registry.PropertiesFile{}
74+
err = json.Unmarshal(yamlFile, propertiesFile)
75+
if err != nil {
76+
return nil, err
77+
}
78+
79+
return propertiesFile, nil
80+
}

0 commit comments

Comments
 (0)