Skip to content

Commit fa4b13b

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 fa4b13b

File tree

3 files changed

+156
-4
lines changed

3 files changed

+156
-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: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"net"
88
"reflect"
9+
"strings"
910
"testing"
1011
"time"
1112

@@ -941,6 +942,116 @@ func TestCatalogImageUpdate(t *testing.T) {
941942
}
942943
}
943944

945+
func TestDependencySkipRange(t *testing.T) {
946+
// Create a CatalogSource that contains the busybox v1 and busybox-dependency v1 images
947+
// Create a Subscription for busybox v1, which has a dependency on busybox-dependency v1.
948+
// Wait for the busybox and busybox2 Subscriptions to succeed
949+
// Wait for the CSVs to succeed
950+
// Update the catalog to point to an image that contains the busybox v2 and busybox-dependency v2 images.
951+
// Wait for the new Subscriptions to succeed and check if they include the new CSVs
952+
// Wait for the CSVs to succeed and confirm that the have the correct Spec.Replaces fields.
953+
defer cleaner.NotifyTestComplete(t, true)
954+
955+
sourceName := genName("catalog-")
956+
packageName := "busybox"
957+
channelName := "alpha"
958+
959+
catSrcImage := "quay.io/olmtest/busybox-dependencies-index"
960+
961+
// Create gRPC CatalogSource
962+
source := &v1alpha1.CatalogSource{
963+
TypeMeta: metav1.TypeMeta{
964+
Kind: v1alpha1.CatalogSourceKind,
965+
APIVersion: v1alpha1.CatalogSourceCRDAPIVersion,
966+
},
967+
ObjectMeta: metav1.ObjectMeta{
968+
Name: sourceName,
969+
Namespace: testNamespace,
970+
},
971+
Spec: v1alpha1.CatalogSourceSpec{
972+
SourceType: v1alpha1.SourceTypeGrpc,
973+
Image: catSrcImage + ":1.0.0",
974+
},
975+
}
976+
977+
crc := newCRClient(t)
978+
source, err := crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Create(source)
979+
require.NoError(t, err)
980+
defer func() {
981+
require.NoError(t, crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Delete(source.GetName(), &metav1.DeleteOptions{}))
982+
}()
983+
984+
// Create a Subscription for busybox
985+
subscriptionName := genName("sub-")
986+
cleanupSubscription := createSubscriptionForCatalog(t, crc, source.GetNamespace(), subscriptionName, source.GetName(), packageName, channelName, "", v1alpha1.ApprovalAutomatic)
987+
defer cleanupSubscription()
988+
989+
// Wait for the Subscription to succeed
990+
subscription, err := fetchSubscription(t, crc, testNamespace, subscriptionName, subscriptionStateAtLatestChecker)
991+
require.NoError(t, err)
992+
require.NotNil(t, subscription)
993+
require.Equal(t, subscription.Status.InstalledCSV, "busybox.v1.0.0")
994+
995+
// Confirm that a subscription was created for busybox-dependency
996+
subscriptionList, err := crc.OperatorsV1alpha1().Subscriptions(source.GetNamespace()).List(metav1.ListOptions{})
997+
require.NoError(t, err)
998+
dependencySubscriptionName := ""
999+
for _, sub := range subscriptionList.Items {
1000+
if strings.HasPrefix(sub.GetName(), "busybox-dependency") {
1001+
dependencySubscriptionName = sub.GetName()
1002+
}
1003+
}
1004+
1005+
require.NotEmpty(t, dependencySubscriptionName)
1006+
// Wait for the Subscription to succeed
1007+
subscription, err = fetchSubscription(t, crc, testNamespace, dependencySubscriptionName, subscriptionStateAtLatestChecker)
1008+
require.NoError(t, err)
1009+
require.NotNil(t, subscription)
1010+
require.Equal(t, subscription.Status.InstalledCSV, "busybox-dependency.v1.0.0")
1011+
1012+
// Update the catalog image
1013+
err = wait.PollImmediate(pollInterval, pollDuration, func() (bool, error) {
1014+
existingSource, err := crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Get(sourceName, metav1.GetOptions{})
1015+
if err != nil {
1016+
return false, err
1017+
}
1018+
existingSource.Spec.Image = catSrcImage + ":2.0.0"
1019+
1020+
source, err = crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Update(existingSource)
1021+
if err == nil {
1022+
return true, nil
1023+
}
1024+
return false, nil
1025+
})
1026+
require.NoError(t, err)
1027+
1028+
// Wait for the busybox v2 Subscription to succeed
1029+
subChecker := func(sub *v1alpha1.Subscription) bool {
1030+
return sub.Status.InstalledCSV == "busybox.v2.0.0"
1031+
}
1032+
subscription, err = fetchSubscription(t, crc, testNamespace, subscriptionName, subChecker)
1033+
require.NoError(t, err)
1034+
require.NotNil(t, subscription)
1035+
1036+
// Wait for busybox v2 csv to succeed and check the replaces field
1037+
csv, err := fetchCSV(t, crc, subscription.Status.CurrentCSV, subscription.GetNamespace(), csvSucceededChecker)
1038+
require.NoError(t, err)
1039+
require.Equal(t, "busybox.v1.0.0", csv.Spec.Replaces)
1040+
1041+
// Wait for the busybox-dependency v2 Subscription to succeed
1042+
subChecker = func(sub *v1alpha1.Subscription) bool {
1043+
return sub.Status.InstalledCSV == "busybox-dependency.v2.0.0"
1044+
}
1045+
subscription, err = fetchSubscription(t, crc, testNamespace, dependencySubscriptionName, subChecker)
1046+
require.NoError(t, err)
1047+
require.NotNil(t, subscription)
1048+
1049+
// Wait for busybox-dependency v2 csv to succeed and check the replaces field
1050+
csv, err = fetchCSV(t, crc, subscription.Status.CurrentCSV, subscription.GetNamespace(), csvSucceededChecker)
1051+
require.NoError(t, err)
1052+
require.Equal(t, "busybox-dependency.v1.0.0", csv.Spec.Replaces)
1053+
}
1054+
9441055
func getOperatorDeployment(c operatorclient.ClientInterface, namespace string, operatorLabels labels.Set) (*appsv1.Deployment, error) {
9451056
deployments, err := c.ListDeploymentsWithLabels(namespace, operatorLabels)
9461057
if err != nil || deployments == nil || len(deployments.Items) != 1 {

0 commit comments

Comments
 (0)