Skip to content

Commit 85e97ab

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 a6acf50 commit 85e97ab

File tree

3 files changed

+157
-4
lines changed

3 files changed

+157
-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: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"net"
88
"reflect"
99
"testing"
10+
"strings"
1011
"time"
1112

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

9271039
// Wait for csv to succeed
9281040
csv, err := fetchCSV(t, crc, subscription.Status.CurrentCSV, subscription.GetNamespace(), csvSucceededChecker)

0 commit comments

Comments
 (0)