Skip to content

Commit b49bee6

Browse files
committed
bug(dependencies) Prioritize APIs from same CatSrc
Problem: When OLM installs an operator that requires dependencies that are provided via multiple operators in different CatalogSources, the API is pulled from any of the CatalogSources that provide the API. Solution: This commit introduces a change so that OLM will generate a list of operators that depend on the API, randomly select one of their sources, and prioritize checking in that CatalogSource for the API prior to checking the remaining CatalogSource for the API.
1 parent 4edf102 commit b49bee6

File tree

5 files changed

+179
-7
lines changed

5 files changed

+179
-7
lines changed

pkg/controller/registry/resolver/evolver.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,15 @@ func (e *NamespaceGenerationEvolver) queryForRequiredAPIs() error {
112112
}
113113
e.gen.MarkAPIChecked(*api)
114114

115+
// identify the initialSource
116+
initialSource := CatalogKey{}
117+
for _, operator := range e.gen.MissingAPIs()[*api] {
118+
initialSource = operator.SourceInfo().Catalog
119+
break
120+
}
121+
115122
// attempt to find a bundle that provides that api
116-
if bundle, key, err := e.querier.FindProvider(*api); err == nil {
123+
if bundle, key, err := e.querier.FindProvider(*api, initialSource); err == nil {
117124
// add a bundle that provides the api to the generation
118125
o, err := NewOperatorFromBundle(bundle, "", "", *key)
119126
if err != nil {

pkg/controller/registry/resolver/querier.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ type SourceRef struct {
2222
}
2323

2424
type SourceQuerier interface {
25-
FindProvider(api opregistry.APIKey) (*opregistry.Bundle, *CatalogKey, error)
25+
FindProvider(api opregistry.APIKey, initialSource CatalogKey) (*opregistry.Bundle, *CatalogKey, error)
2626
FindBundle(pkgName, channelName, bundleName string, initialSource CatalogKey) (*opregistry.Bundle, *CatalogKey, error)
2727
FindLatestBundle(pkgName, channelName string, initialSource CatalogKey) (*opregistry.Bundle, *CatalogKey, error)
2828
FindReplacement(currentVersion *semver.Version, bundleName, pkgName, channelName string, initialSource CatalogKey) (*opregistry.Bundle, *CatalogKey, error)
@@ -48,7 +48,18 @@ func (q *NamespaceSourceQuerier) Queryable() error {
4848
return nil
4949
}
5050

51-
func (q *NamespaceSourceQuerier) FindProvider(api opregistry.APIKey) (*opregistry.Bundle, *CatalogKey, error) {
51+
func (q *NamespaceSourceQuerier) FindProvider(api opregistry.APIKey, initialSource CatalogKey) (*opregistry.Bundle, *CatalogKey, error) {
52+
if initialSource.Name != "" && initialSource.Namespace != "" {
53+
source, ok := q.sources[initialSource]
54+
if ok {
55+
if bundle, err := source.GetBundleThatProvides(context.TODO(), api.Group, api.Version, api.Kind); err == nil {
56+
return bundle, &initialSource, nil
57+
}
58+
if bundle, err := source.GetBundleThatProvides(context.TODO(), api.Plural+"."+api.Group, api.Version, api.Kind); err == nil {
59+
return bundle, &initialSource, nil
60+
}
61+
}
62+
}
5263
for key, source := range q.sources {
5364
if bundle, err := source.GetBundleThatProvides(context.TODO(), api.Group, api.Version, api.Kind); err == nil {
5465
return bundle, &key, nil

pkg/controller/registry/resolver/querier_test.go

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,20 +106,33 @@ func TestNamespaceSourceQuerier_Queryable(t *testing.T) {
106106

107107
func TestNamespaceSourceQuerier_FindProvider(t *testing.T) {
108108
fakeSource := fakes.FakeInterface{}
109+
fakeSource2 := fakes.FakeInterface{}
109110
sources := map[CatalogKey]client.Interface{
110111
CatalogKey{"test", "ns"}: &fakeSource,
112+
CatalogKey{"test2", "ns"}: &fakeSource2,
111113
}
112114

113115
bundle := opregistry.NewBundle("test", "testPkg", "testChannel")
116+
bundle2 := opregistry.NewBundle("test2", "testPkg2", "testChannel2")
114117
fakeSource.GetBundleThatProvidesStub = func(ctx context.Context, group, version, kind string) (*opregistry.Bundle, error) {
118+
if group != "group" || version != "version" || kind != "kind" {
119+
return nil, fmt.Errorf("Not Found")
120+
}
115121
return bundle, nil
116122
}
123+
fakeSource2.GetBundleThatProvidesStub = func(ctx context.Context, group, version, kind string) (*opregistry.Bundle, error) {
124+
if group != "group2" || version != "version2" || kind != "kind2" {
125+
return nil, fmt.Errorf("Not Found")
126+
}
127+
return bundle2, nil
128+
}
117129

118130
type fields struct {
119131
sources map[CatalogKey]client.Interface
120132
}
121133
type args struct {
122134
api opregistry.APIKey
135+
catalogKey CatalogKey
123136
}
124137
type out struct {
125138
bundle *opregistry.Bundle
@@ -138,6 +151,7 @@ func TestNamespaceSourceQuerier_FindProvider(t *testing.T) {
138151
},
139152
args: args{
140153
api: opregistry.APIKey{"group", "version", "kind", "plural"},
154+
catalogKey: CatalogKey{},
141155
},
142156
out: out{
143157
bundle: bundle,
@@ -151,23 +165,52 @@ func TestNamespaceSourceQuerier_FindProvider(t *testing.T) {
151165
},
152166
args: args{
153167
api: opregistry.APIKey{"group", "version", "kind", "plural"},
168+
catalogKey: CatalogKey{},
154169
},
155170
out: out{
156171
bundle: nil,
157172
key: nil,
158173
err: fmt.Errorf("group/version/kind (plural) not provided by a package in any CatalogSource"),
159174
},
160175
},
176+
{
177+
fields: fields{
178+
sources: sources,
179+
},
180+
args: args{
181+
api: opregistry.APIKey{"group2", "version2", "kind2", "plural2"},
182+
catalogKey: CatalogKey{Name: "test2", Namespace: "ns"},
183+
},
184+
out: out{
185+
bundle: bundle2,
186+
key: &CatalogKey{Name: "test2", Namespace: "ns"},
187+
err: nil,
188+
},
189+
},
190+
{
191+
fields: fields{
192+
sources: sources,
193+
},
194+
args: args{
195+
api: opregistry.APIKey{"group2", "version2", "kind2", "plural2"},
196+
catalogKey: CatalogKey{Name: "test3", Namespace: "ns"},
197+
},
198+
out: out{
199+
bundle: bundle2,
200+
key: &CatalogKey{Name: "test2", Namespace: "ns"},
201+
err: nil,
202+
},
203+
},
161204
}
162205
for _, tt := range tests {
163206
t.Run(tt.name, func(t *testing.T) {
164207
q := &NamespaceSourceQuerier{
165208
sources: tt.fields.sources,
166209
}
167-
bundle, key, err := q.FindProvider(tt.args.api)
168-
require.Equal(t, err, tt.out.err)
169-
require.Equal(t, bundle, tt.out.bundle)
170-
require.Equal(t, key, tt.out.key)
210+
bundle, key, err := q.FindProvider(tt.args.api, tt.args.catalogKey)
211+
require.Equal(t, tt.out.err, err)
212+
require.Equal(t, tt.out.bundle, bundle)
213+
require.Equal(t, tt.out.key, key)
171214
})
172215
}
173216
}

test/e2e/subscription_e2e_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1362,6 +1362,48 @@ func TestCreateNewSubscriptionWithPodConfig(t *testing.T) {
13621362
checkDeploymentWithPodConfiguration(t, kubeClient, csv, podConfig.Env, podConfig.Volumes, podConfig.VolumeMounts)
13631363
}
13641364

1365+
1366+
func TestCreateNewSubscriptionWithDependencies(t *testing.T) {
1367+
defer cleaner.NotifyTestComplete(t, true)
1368+
1369+
kubeClient := newKubeClient(t)
1370+
crClient := newCRClient(t)
1371+
1372+
permissions := deploymentPermissions(t)
1373+
1374+
catsrc, subSpec, catsrcCleanup := newCatalogSourceWithDependencies(t, kubeClient, crClient, "podconfig", testNamespace, permissions)
1375+
defer catsrcCleanup()
1376+
1377+
// Ensure that the catalog source is resolved before we create a subscription.
1378+
_, err := fetchCatalogSource(t, crClient, catsrc.GetName(), testNamespace, catalogSourceRegistryPodSynced)
1379+
require.NoError(t, err)
1380+
1381+
// Create duplicates of the CatalogSource
1382+
for i := 0; i < 10; i++ {
1383+
duplicateCatsrc, _, duplicateCatSrcCleanup := newCatalogSourceWithDependencies(t, kubeClient, crClient, "podconfig", testNamespace, permissions)
1384+
defer duplicateCatSrcCleanup()
1385+
1386+
// Ensure that the catalog source is resolved before we create a subscription.
1387+
_, err = fetchCatalogSource(t, crClient, duplicateCatsrc.GetName(), testNamespace, catalogSourceRegistryPodSynced)
1388+
require.NoError(t, err)
1389+
}
1390+
1391+
// Create a subscription that has a dependency
1392+
subscriptionName := genName("podconfig-sub-")
1393+
cleanupSubscription := createSubscriptionForCatalogWithSpec(t, crClient, testNamespace, subscriptionName, subSpec)
1394+
defer cleanupSubscription()
1395+
1396+
subscription, err := fetchSubscription(t, crClient, testNamespace, subscriptionName, subscriptionStateAtLatestChecker)
1397+
require.NoError(t, err)
1398+
require.NotNil(t, subscription)
1399+
1400+
// Check that a single catalog source was used to resolve the InstallPlan
1401+
installPlan, err:= fetchInstallPlan(t, crClient, subscription.Status.InstallPlanRef.Name, buildInstallPlanPhaseCheckFunc(v1alpha1.InstallPlanPhaseComplete))
1402+
require.NoError(t, err)
1403+
require.Len(t, installPlan.Status.CatalogSources,1)
1404+
1405+
}
1406+
13651407
func checkDeploymentWithPodConfiguration(t *testing.T, client operatorclient.ClientInterface, csv *v1alpha1.ClusterServiceVersion, envVar []corev1.EnvVar, volumes []corev1.Volume, volumeMounts []corev1.VolumeMount) {
13661408
resolver := install.StrategyResolver{}
13671409

test/e2e/user_defined_sa_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,75 @@ func newCatalogSource(t *testing.T, kubeclient operatorclient.ClientInterface, c
310310
return
311311
}
312312

313+
314+
func newCatalogSourceWithDependencies(t *testing.T, kubeclient operatorclient.ClientInterface, crclient versioned.Interface, prefix, namespace string, permissions []install.StrategyDeploymentPermissions) (catsrc *v1alpha1.CatalogSource, subscriptionSpec *v1alpha1.SubscriptionSpec, cleanup cleanupFunc) {
315+
crdPlural := genName("ins")
316+
crdName := crdPlural + ".cluster.com"
317+
318+
crd := apiextensions.CustomResourceDefinition{
319+
ObjectMeta: metav1.ObjectMeta{
320+
Name: crdName,
321+
},
322+
Spec: apiextensions.CustomResourceDefinitionSpec{
323+
Group: "cluster.com",
324+
Version: "v1alpha1",
325+
Names: apiextensions.CustomResourceDefinitionNames{
326+
Plural: crdPlural,
327+
Singular: crdPlural,
328+
Kind: crdPlural,
329+
ListKind: "list" + crdPlural,
330+
},
331+
Scope: "Namespaced",
332+
},
333+
}
334+
335+
prefixFunc := func(s string) string {
336+
return fmt.Sprintf("%s-%s-", prefix, s)
337+
}
338+
339+
// Create CSV
340+
packageName1 := genName(prefixFunc("package"))
341+
packageName2 := genName(prefixFunc("package"))
342+
stableChannel := "stable"
343+
344+
namedStrategy := newNginxInstallStrategy(genName(prefixFunc("dep")), permissions, nil)
345+
csvA := newCSV("nginx-req-dep", namespace, "", semver.MustParse("0.1.0"), nil, []apiextensions.CustomResourceDefinition{crd}, namedStrategy)
346+
csvB := newCSV("nginx-dependency", namespace, "", semver.MustParse("0.1.0"), []apiextensions.CustomResourceDefinition{crd}, nil, namedStrategy)
347+
348+
// Create PackageManifests
349+
manifests := []registry.PackageManifest{
350+
{
351+
PackageName: packageName1,
352+
Channels: []registry.PackageChannel{
353+
{Name: stableChannel, CurrentCSVName: csvA.GetName()},
354+
},
355+
DefaultChannelName: stableChannel,
356+
},
357+
{
358+
PackageName: packageName2,
359+
Channels: []registry.PackageChannel{
360+
{Name: stableChannel, CurrentCSVName: csvB.GetName()},
361+
},
362+
DefaultChannelName: stableChannel,
363+
},
364+
}
365+
366+
catalogSourceName := genName(prefixFunc("catsrc"))
367+
catsrc, cleanup = createInternalCatalogSource(t, kubeclient, crclient, catalogSourceName, namespace, manifests, []apiextensions.CustomResourceDefinition{crd}, []v1alpha1.ClusterServiceVersion{csvA, csvB})
368+
require.NotNil(t, catsrc)
369+
require.NotNil(t, cleanup)
370+
371+
subscriptionSpec = &v1alpha1.SubscriptionSpec{
372+
CatalogSource: catsrc.GetName(),
373+
CatalogSourceNamespace: catsrc.GetNamespace(),
374+
Package: packageName1,
375+
Channel: stableChannel,
376+
StartingCSV: csvA.GetName(),
377+
InstallPlanApproval: v1alpha1.ApprovalAutomatic,
378+
}
379+
return
380+
}
381+
313382
func mustHaveCondition(t *testing.T, ip *v1alpha1.InstallPlan, conditionType v1alpha1.InstallPlanConditionType) (condition *v1alpha1.InstallPlanCondition) {
314383
for i := range ip.Status.Conditions {
315384
if ip.Status.Conditions[i].Type == conditionType {

0 commit comments

Comments
 (0)