Skip to content

Commit 0cc76a2

Browse files
committed
Fix Operator Generation code
Problem: If an operator is being upgraded that provides a required API whose GVK has not changed since the previous version of the operator and uses a skipRange instead of the Spec.Replaces field, OLM will: * Add the new operator to the generation, and marking the APIs it provides as "present". * Remove the old operator from the generation, marking the APIs it provides as "absent", despite being provided by the new version of the operator. * Attempt to resolve the "missing" APIs, overwriting the new version of the operator with a copy that does not have its Spec.Replaces field set. This causes OLM to fail the upgrade, where the old operator's CSV will not be replaced and the new operators CSV will run into an intercepting API Provider issue. Solution: Update OLM to remove the old operator from the current generation before adding the new operator to the generation.
1 parent ed17b12 commit 0cc76a2

File tree

3 files changed

+155
-4
lines changed

3 files changed

+155
-4
lines changed

pkg/controller/registry/resolver/evolver.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,14 @@ func (e *NamespaceGenerationEvolver) checkForUpdates() error {
6868
return errors.Wrap(err, "error parsing bundle")
6969
}
7070
o.SetReplaces(op.Identifier())
71+
72+
// Remove the old operator and the APIs it provides before adding the new operator to the generation.
73+
e.gen.RemoveOperator(op)
74+
75+
// Add the new operator and the APIs it provides to the generation.
7176
if err := e.gen.AddOperator(o); err != nil {
7277
return errors.Wrap(err, "error calculating generation changes due to new bundle")
7378
}
74-
e.gen.RemoveOperator(op)
7579
}
7680
return nil
7781
}

pkg/controller/registry/resolver/resolver_test.go

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ func TestNamespaceResolver(t *testing.T) {
120120
},
121121
lookups: []v1alpha1.BundleLookup{
122122
{
123-
Path: "quay.io/test/bundle@sha256:abcd",
123+
Path: "quay.io/test/bundle@sha256:abcd",
124124
Identifier: "b.v1",
125125
CatalogSourceRef: &corev1.ObjectReference{
126126
Namespace: catalog.Namespace,
@@ -234,9 +234,9 @@ func TestNamespaceResolver(t *testing.T) {
234234
steps: [][]*v1alpha1.Step{},
235235
lookups: []v1alpha1.BundleLookup{
236236
{
237-
Path: "quay.io/test/bundle@sha256:abcd",
237+
Path: "quay.io/test/bundle@sha256:abcd",
238238
Identifier: "a.v2",
239-
Replaces: "a.v1",
239+
Replaces: "a.v1",
240240
CatalogSourceRef: &corev1.ObjectReference{
241241
Namespace: catalog.Namespace,
242242
Name: catalog.Name,
@@ -425,6 +425,43 @@ func TestNamespaceResolver(t *testing.T) {
425425
},
426426
},
427427
},
428+
{
429+
// This test uses logic that implements the FakeSourceQuerier to ensure
430+
// that the required API is provided by the new Operator.
431+
//
432+
// Background:
433+
// OLM used to add the new operator to the generation before removing
434+
// the old operator from the generation. The logic that removes an operator
435+
// from the current generation removes the APIs it provides from the list of
436+
// "available" APIs. This caused OLM to search for an operator that provides the API.
437+
// If the operator that provides the API uses a skipRange rather than the Spec.Replaces
438+
// field, the Replaces field is set to an empty string, causing OLM to fail to upgrade.
439+
name: "InstalledSubs/ExistingOperators/OldCSVsReplaced",
440+
clusterState: []runtime.Object{
441+
existingSub(namespace, "a.v1", "a", "alpha", catalog),
442+
existingSub(namespace, "b.v1", "b", "beta", catalog),
443+
existingOperator(namespace, "a.v1", "a", "alpha", "", nil, Requires1, nil, nil),
444+
existingOperator(namespace, "b.v1", "b", "beta", "", Provides1, nil, nil, nil),
445+
},
446+
querier: NewFakeSourceQuerier(map[CatalogKey][]*api.Bundle{
447+
catalog: {
448+
bundle("a.v1", "a", "alpha", "", nil, nil, nil, nil),
449+
bundle("a.v2", "a", "alpha", "a.v1", nil, Requires1, nil, nil),
450+
bundle("b.v1", "b", "beta", "", Provides1, nil, nil, nil),
451+
bundle("b.v2", "b", "beta", "b.v1", Provides1, nil, nil, nil),
452+
},
453+
}),
454+
out: out{
455+
steps: [][]*v1alpha1.Step{
456+
bundleSteps(bundle("a.v2", "a", "alpha", "a.v1", nil, Requires1, nil, nil), namespace, "", catalog),
457+
bundleSteps(bundle("b.v2", "b", "beta", "b.v1", Provides1, nil, nil, nil), namespace, "", catalog),
458+
},
459+
subs: []*v1alpha1.Subscription{
460+
updatedSub(namespace, "a.v2", "a", "alpha", catalog),
461+
updatedSub(namespace, "b.v2", "b", "beta", catalog),
462+
},
463+
},
464+
},
428465
}
429466
for _, tt := range tests {
430467
t.Run(tt.name, func(t *testing.T) {

test/e2e/catalog_e2e_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"net"
99
"os"
1010
"reflect"
11+
"strings"
1112
"time"
1213

1314
"github.com/blang/semver"
@@ -936,6 +937,115 @@ var _ = Describe("Catalog", func() {
936937
GinkgoT().Errorf("latest version of operator not installed: catalog souce update failed")
937938
}
938939
})
940+
It("Dependency has correct replaces field", func() {
941+
// Create a CatalogSource that contains the busybox v1 and busybox-dependency v1 images
942+
// Create a Subscription for busybox v1, which has a dependency on busybox-dependency v1.
943+
// Wait for the busybox and busybox2 Subscriptions to succeed
944+
// Wait for the CSVs to succeed
945+
// Update the catalog to point to an image that contains the busybox v2 and busybox-dependency v2 images.
946+
// Wait for the new Subscriptions to succeed and check if they include the new CSVs
947+
// Wait for the CSVs to succeed and confirm that the have the correct Spec.Replaces fields.
948+
defer cleaner.NotifyTestComplete(GinkgoT(), true)
949+
950+
sourceName := genName("catalog-")
951+
packageName := "busybox"
952+
channelName := "alpha"
953+
954+
catSrcImage := "quay.io/olmtest/busybox-dependencies-index"
955+
956+
// Create gRPC CatalogSource
957+
source := &v1alpha1.CatalogSource{
958+
TypeMeta: metav1.TypeMeta{
959+
Kind: v1alpha1.CatalogSourceKind,
960+
APIVersion: v1alpha1.CatalogSourceCRDAPIVersion,
961+
},
962+
ObjectMeta: metav1.ObjectMeta{
963+
Name: sourceName,
964+
Namespace: testNamespace,
965+
},
966+
Spec: v1alpha1.CatalogSourceSpec{
967+
SourceType: v1alpha1.SourceTypeGrpc,
968+
Image: catSrcImage + ":1.0.0",
969+
},
970+
}
971+
972+
crc := newCRClient(GinkgoT())
973+
source, err := crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Create(context.TODO(), source, metav1.CreateOptions{})
974+
require.NoError(GinkgoT(), err)
975+
defer func() {
976+
require.NoError(GinkgoT(), crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Delete(context.TODO(), source.GetName(), metav1.DeleteOptions{}))
977+
}()
978+
979+
// Create a Subscription for busybox
980+
subscriptionName := genName("sub-")
981+
cleanupSubscription := createSubscriptionForCatalog(GinkgoT(), crc, source.GetNamespace(), subscriptionName, source.GetName(), packageName, channelName, "", v1alpha1.ApprovalAutomatic)
982+
defer cleanupSubscription()
983+
984+
// Wait for the Subscription to succeed
985+
subscription, err := fetchSubscription(GinkgoT(), crc, testNamespace, subscriptionName, subscriptionStateAtLatestChecker)
986+
require.NoError(GinkgoT(), err)
987+
require.NotNil(GinkgoT(), subscription)
988+
require.Equal(GinkgoT(), subscription.Status.InstalledCSV, "busybox.v1.0.0")
989+
990+
// Confirm that a subscription was created for busybox-dependency
991+
subscriptionList, err := crc.OperatorsV1alpha1().Subscriptions(source.GetNamespace()).List(context.TODO(), metav1.ListOptions{})
992+
require.NoError(GinkgoT(), err)
993+
dependencySubscriptionName := ""
994+
for _, sub := range subscriptionList.Items {
995+
if strings.HasPrefix(sub.GetName(), "busybox-dependency") {
996+
dependencySubscriptionName = sub.GetName()
997+
}
998+
}
999+
1000+
require.NotEmpty(GinkgoT(), dependencySubscriptionName)
1001+
// Wait for the Subscription to succeed
1002+
subscription, err = fetchSubscription(GinkgoT(), crc, testNamespace, dependencySubscriptionName, subscriptionStateAtLatestChecker)
1003+
require.NoError(GinkgoT(), err)
1004+
require.NotNil(GinkgoT(), subscription)
1005+
require.Equal(GinkgoT(), subscription.Status.InstalledCSV, "busybox-dependency.v1.0.0")
1006+
1007+
// Update the catalog image
1008+
err = wait.PollImmediate(pollInterval, pollDuration, func() (bool, error) {
1009+
existingSource, err := crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Get(context.TODO(), sourceName, metav1.GetOptions{})
1010+
if err != nil {
1011+
return false, err
1012+
}
1013+
existingSource.Spec.Image = catSrcImage + ":2.0.0"
1014+
1015+
source, err = crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Update(context.TODO(), existingSource, metav1.UpdateOptions{})
1016+
if err == nil {
1017+
return true, nil
1018+
}
1019+
return false, nil
1020+
})
1021+
require.NoError(GinkgoT(), err)
1022+
1023+
// Wait for the busybox v2 Subscription to succeed
1024+
subChecker := func(sub *v1alpha1.Subscription) bool {
1025+
return sub.Status.InstalledCSV == "busybox.v2.0.0"
1026+
}
1027+
subscription, err = fetchSubscription(GinkgoT(), crc, testNamespace, subscriptionName, subChecker)
1028+
require.NoError(GinkgoT(), err)
1029+
require.NotNil(GinkgoT(), subscription)
1030+
1031+
// Wait for busybox v2 csv to succeed and check the replaces field
1032+
csv, err := fetchCSV(GinkgoT(), crc, subscription.Status.CurrentCSV, subscription.GetNamespace(), csvSucceededChecker)
1033+
require.NoError(GinkgoT(), err)
1034+
require.Equal(GinkgoT(), "busybox.v1.0.0", csv.Spec.Replaces)
1035+
1036+
// Wait for the busybox-dependency v2 Subscription to succeed
1037+
subChecker = func(sub *v1alpha1.Subscription) bool {
1038+
return sub.Status.InstalledCSV == "busybox-dependency.v2.0.0"
1039+
}
1040+
subscription, err = fetchSubscription(GinkgoT(), crc, testNamespace, dependencySubscriptionName, subChecker)
1041+
require.NoError(GinkgoT(), err)
1042+
require.NotNil(GinkgoT(), subscription)
1043+
1044+
// Wait for busybox-dependency v2 csv to succeed and check the replaces field
1045+
csv, err = fetchCSV(GinkgoT(), crc, subscription.Status.CurrentCSV, subscription.GetNamespace(), csvSucceededChecker)
1046+
require.NoError(GinkgoT(), err)
1047+
require.Equal(GinkgoT(), "busybox-dependency.v1.0.0", csv.Spec.Replaces)
1048+
})
9391049
})
9401050

9411051
const (

0 commit comments

Comments
 (0)