github.com/operator-framework/operator-lifecycle-manager@v0.30.0/test/e2e/crd_e2e_test.go (about)

     1  package e2e
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	operatorsv1 "github.com/operator-framework/api/pkg/operators/v1"
     7  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/bundle"
     8  	"time"
     9  
    10  	"github.com/blang/semver/v4"
    11  	. "github.com/onsi/ginkgo/v2"
    12  	. "github.com/onsi/gomega"
    13  	operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
    14  	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
    15  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
    16  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
    17  	"github.com/operator-framework/operator-lifecycle-manager/test/e2e/ctx"
    18  	corev1 "k8s.io/api/core/v1"
    19  
    20  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    21  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    22  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    23  )
    24  
    25  var _ = Describe("CRD Versions", func() {
    26  	var (
    27  		generatedNamespace corev1.Namespace
    28  		c                  operatorclient.ClientInterface
    29  		crc                versioned.Interface
    30  	)
    31  
    32  	BeforeEach(func() {
    33  		c = ctx.Ctx().KubeClient()
    34  		crc = ctx.Ctx().OperatorClient()
    35  		namespace := genName("crd-e2e-")
    36  		og := operatorsv1.OperatorGroup{
    37  			ObjectMeta: metav1.ObjectMeta{
    38  				Name:      fmt.Sprintf("%s-operatorgroup", namespace),
    39  				Namespace: namespace,
    40  				Annotations: map[string]string{
    41  					// Reduce the bundle unpack timeout to ensure error states are reached quickly
    42  					bundle.BundleUnpackTimeoutAnnotationKey: "5s",
    43  				},
    44  			},
    45  		}
    46  		generatedNamespace = SetupGeneratedTestNamespaceWithOperatorGroup(namespace, og)
    47  	})
    48  
    49  	AfterEach(func() {
    50  		TeardownNamespace(generatedNamespace.GetName())
    51  	})
    52  
    53  	// issue: https://github.com/operator-framework/operator-lifecycle-manager/issues/2640
    54  	It("[FLAKE] creates v1 CRDs with a v1 schema successfully", func() {
    55  		By("v1 crds with a valid openapiv3 schema should be created successfully by OLM")
    56  
    57  		mainPackageName := genName("nginx-update2-")
    58  		mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName)
    59  		stableChannel := "stable"
    60  
    61  		crdPlural := genName("ins-")
    62  		crdName := crdPlural + ".cluster.com"
    63  		v1crd := apiextensionsv1.CustomResourceDefinition{
    64  			ObjectMeta: metav1.ObjectMeta{
    65  				Name: crdName,
    66  			},
    67  			Spec: apiextensionsv1.CustomResourceDefinitionSpec{
    68  				Scope: apiextensionsv1.NamespaceScoped,
    69  				Group: "cluster.com",
    70  				Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
    71  					{
    72  						Name:    "v1alpha1",
    73  						Served:  true,
    74  						Storage: true,
    75  						Schema: &apiextensionsv1.CustomResourceValidation{
    76  							OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
    77  								Type:        "object",
    78  								Description: "my crd schema",
    79  							},
    80  						},
    81  					},
    82  				},
    83  			},
    84  		}
    85  
    86  		mainCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), nil, nil, nil)
    87  		mainCatalogName := genName("mock-ocs-main-update2-")
    88  		mainManifests := []registry.PackageManifest{
    89  			{
    90  				PackageName: mainPackageName,
    91  				Channels: []registry.PackageChannel{
    92  					{Name: stableChannel, CurrentCSVName: mainPackageStable},
    93  				},
    94  				DefaultChannelName: stableChannel,
    95  			},
    96  		}
    97  
    98  		By("Create the catalog sources")
    99  		_, cleanupMainCatalogSource := createV1CRDInternalCatalogSource(GinkgoT(), c, crc, mainCatalogName, generatedNamespace.GetName(), mainManifests, []apiextensionsv1.CustomResourceDefinition{v1crd}, []operatorsv1alpha1.ClusterServiceVersion{mainCSV})
   100  		defer cleanupMainCatalogSource()
   101  		defer func() {
   102  			_ = crc.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Delete(context.TODO(), mainCSV.GetName(), metav1.DeleteOptions{})
   103  			_ = c.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.TODO(), v1crd.GetName(), metav1.DeleteOptions{})
   104  		}()
   105  
   106  		By("Attempt to get the catalog source before creating install plan")
   107  
   108  		_, err := fetchCatalogSourceOnStatus(crc, mainCatalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced())
   109  		Expect(err).ToNot(HaveOccurred())
   110  
   111  		subscriptionName := genName("sub-nginx-update2-")
   112  		subscriptionCleanup := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic)
   113  		defer subscriptionCleanup()
   114  
   115  		subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker())
   116  		Expect(err).ToNot(HaveOccurred())
   117  		Expect(subscription).ToNot(Equal(nil))
   118  		Expect(subscription.Status.InstallPlanRef).ToNot(Equal(nil))
   119  		Expect(mainCSV.GetName()).To(Equal(subscription.Status.CurrentCSV))
   120  
   121  		installPlanName := subscription.Status.InstallPlanRef.Name
   122  
   123  		By("Wait for InstallPlan to be status: Complete before checking resource presence")
   124  		fetchedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete))
   125  		Expect(err).ToNot(HaveOccurred())
   126  		GinkgoT().Logf("Install plan %s fetched with status %s", fetchedInstallPlan.GetName(), fetchedInstallPlan.Status.Phase)
   127  		Expect(fetchedInstallPlan.Status.Phase).To(Equal(operatorsv1alpha1.InstallPlanPhaseComplete))
   128  	})
   129  	It("allows a CRD upgrade that doesn't cause data loss", func() {
   130  		By(`Create a CRD on cluster with v1alpha1 (storage)`)
   131  		By(`Update that CRD with v1alpha2 (storage), v1alpha1 (served)`)
   132  		By(`Now the CRD should have two versions in status.storedVersions`)
   133  		By(`Now make a catalog with a CRD with just v1alpha2 (storage)`)
   134  		By(`That should fail because v1alpha1 is still in status.storedVersions - risk of data loss`)
   135  		By(`Update the CRD status to remove the v1alpha1`)
   136  		By(`Now the installplan should succeed`)
   137  
   138  		By("manually editing the storage versions in the existing CRD status")
   139  
   140  		crdPlural := genName("ins-v1-")
   141  		crdName := crdPlural + ".cluster.com"
   142  		crdGroup := "cluster.com"
   143  
   144  		oldCRD := &apiextensionsv1.CustomResourceDefinition{
   145  			ObjectMeta: metav1.ObjectMeta{
   146  				Name: crdName,
   147  			},
   148  			Spec: apiextensionsv1.CustomResourceDefinitionSpec{
   149  				Group: crdGroup,
   150  				Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
   151  					{
   152  						Name:    "v1alpha1",
   153  						Served:  true,
   154  						Storage: true,
   155  						Schema: &apiextensionsv1.CustomResourceValidation{
   156  							OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
   157  								Type:        "object",
   158  								Description: "my crd schema",
   159  							},
   160  						},
   161  					},
   162  				},
   163  				Names: apiextensionsv1.CustomResourceDefinitionNames{
   164  					Plural:   crdPlural,
   165  					Singular: crdPlural,
   166  					Kind:     crdPlural,
   167  					ListKind: "list" + crdPlural,
   168  				},
   169  				Scope: apiextensionsv1.NamespaceScoped,
   170  			},
   171  		}
   172  		_, err := c.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Create(context.TODO(), oldCRD, metav1.CreateOptions{})
   173  		Expect(err).ToNot(HaveOccurred(), "error creating old CRD")
   174  
   175  		By("wrap CRD update in a poll because of the object has been modified related errors")
   176  		Eventually(func() error {
   177  			oldCRD, err = c.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), oldCRD.GetName(), metav1.GetOptions{})
   178  			if err != nil {
   179  				return err
   180  			}
   181  			GinkgoT().Logf("old crd status stored versions: %#v", oldCRD.Status.StoredVersions)
   182  
   183  			By("set v1alpha1 to no longer stored")
   184  			oldCRD.Spec.Versions[0].Storage = false
   185  			By("update CRD on-cluster with a new version")
   186  			oldCRD.Spec.Versions = append(oldCRD.Spec.Versions, apiextensionsv1.CustomResourceDefinitionVersion{
   187  				Name:    "v1alpha2",
   188  				Served:  true,
   189  				Storage: true,
   190  				Schema: &apiextensionsv1.CustomResourceValidation{
   191  					OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
   192  						Type: "object",
   193  					},
   194  				},
   195  			})
   196  
   197  			updatedCRD, err := c.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Update(context.TODO(), oldCRD, metav1.UpdateOptions{})
   198  			if err != nil {
   199  				return err
   200  			}
   201  			GinkgoT().Logf("updated crd status stored versions: %#v", updatedCRD.Status.StoredVersions) // both v1alpha1 and v1alpha2 should be in the status
   202  			return nil
   203  		}).Should(BeNil())
   204  
   205  		By("create CSV and catalog with just the catalog CRD")
   206  		catalogCRD := apiextensionsv1.CustomResourceDefinition{
   207  			ObjectMeta: metav1.ObjectMeta{
   208  				Name: crdName,
   209  			},
   210  			Spec: apiextensionsv1.CustomResourceDefinitionSpec{
   211  				Group: crdGroup,
   212  				Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
   213  					{
   214  						Name:    "v1alpha2",
   215  						Served:  true,
   216  						Storage: true,
   217  						Schema: &apiextensionsv1.CustomResourceValidation{
   218  							OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
   219  								Type:        "object",
   220  								Description: "my crd schema",
   221  							},
   222  						},
   223  					},
   224  				},
   225  				Names: apiextensionsv1.CustomResourceDefinitionNames{
   226  					Plural:   crdPlural,
   227  					Singular: crdPlural,
   228  					Kind:     crdPlural,
   229  					ListKind: "list" + crdPlural,
   230  				},
   231  				Scope: apiextensionsv1.NamespaceScoped,
   232  			},
   233  		}
   234  
   235  		mainPackageName := genName("nginx-update2-")
   236  		mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName)
   237  		stableChannel := "stable"
   238  		catalogCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{catalogCRD}, nil, nil)
   239  		defer func() {
   240  			_ = crc.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Delete(context.TODO(), catalogCSV.GetName(), metav1.DeleteOptions{})
   241  			_ = c.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.TODO(), catalogCRD.GetName(), metav1.DeleteOptions{})
   242  		}()
   243  
   244  		mainCatalogName := genName("mock-ocs-main-update2-")
   245  		mainManifests := []registry.PackageManifest{
   246  			{
   247  				PackageName: mainPackageName,
   248  				Channels: []registry.PackageChannel{
   249  					{Name: stableChannel, CurrentCSVName: mainPackageStable},
   250  				},
   251  				DefaultChannelName: stableChannel,
   252  			},
   253  		}
   254  
   255  		By("Create the catalog sources")
   256  		_, cleanupMainCatalogSource := createInternalCatalogSource(c, crc, mainCatalogName, generatedNamespace.GetName(), mainManifests, []apiextensionsv1.CustomResourceDefinition{catalogCRD}, []operatorsv1alpha1.ClusterServiceVersion{catalogCSV})
   257  		defer cleanupMainCatalogSource()
   258  
   259  		By("Attempt to get the catalog source before creating install plan")
   260  		_, err = fetchCatalogSourceOnStatus(crc, mainCatalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced())
   261  		Expect(err).ToNot(HaveOccurred())
   262  
   263  		subscriptionName := genName("sub-nginx-update2-")
   264  		_ = createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic)
   265  
   266  		subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker())
   267  		Expect(err).ToNot(HaveOccurred())
   268  		Expect(subscription).ToNot(BeNil())
   269  		Expect(subscription.Status.InstallPlanRef).ToNot(Equal(nil))
   270  		Expect(catalogCSV.GetName()).To(Equal(subscription.Status.CurrentCSV))
   271  
   272  		By("Check the error on the installplan - should be related to data loss and the CRD upgrade missing a stored version (v1alpha1)")
   273  		Eventually(
   274  			func() (*operatorsv1alpha1.InstallPlan, error) {
   275  				return crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Get(context.TODO(), subscription.Status.InstallPlanRef.Name, metav1.GetOptions{})
   276  			},
   277  			90*time.Second, // exhaust retries
   278  		).Should(WithTransform(
   279  			func(v *operatorsv1alpha1.InstallPlan) operatorsv1alpha1.InstallPlanPhase { return v.Status.Phase },
   280  			Equal(operatorsv1alpha1.InstallPlanPhaseFailed),
   281  		))
   282  
   283  		By("update CRD status to remove the v1alpha1 stored version")
   284  		newCRD, err := c.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), oldCRD.GetName(), metav1.GetOptions{})
   285  		Expect(err).ToNot(HaveOccurred(), "error getting new CRD")
   286  		newCRD.Status.StoredVersions = []string{"v1alpha2"}
   287  		newCRD, err = c.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().UpdateStatus(context.TODO(), newCRD, metav1.UpdateOptions{})
   288  		Expect(err).ToNot(HaveOccurred(), "error updating new CRD")
   289  		GinkgoT().Logf("new crd status stored versions: %#v", newCRD.Status.StoredVersions) // only v1alpha2 should be in the status now
   290  
   291  		By("install should now succeed")
   292  		oldInstallPlanRef := subscription.Status.InstallPlanRef.Name
   293  		err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Delete(context.TODO(), subscription.Status.InstallPlanRef.Name, metav1.DeleteOptions{})
   294  		Expect(err).ToNot(HaveOccurred(), "error deleting failed install plan")
   295  		By("remove old subscription")
   296  		err = crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Delete(context.TODO(), subscription.GetName(), metav1.DeleteOptions{})
   297  		Expect(err).ToNot(HaveOccurred(), "error deleting old subscription")
   298  		By("remove old csv")
   299  		err = crc.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Delete(context.TODO(), mainPackageStable, metav1.DeleteOptions{})
   300  		Expect(err).ToNot(HaveOccurred(), "error deleting old subscription")
   301  
   302  		By("recreate subscription")
   303  		subscriptionNameNew := genName("sub-nginx-update2-new-")
   304  		_ = createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionNameNew, mainCatalogName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic)
   305  
   306  		subscription, err = fetchSubscription(crc, generatedNamespace.GetName(), subscriptionNameNew, subscriptionHasInstallPlanChecker())
   307  		Expect(err).ToNot(HaveOccurred())
   308  		Expect(subscription).ToNot(BeNil())
   309  		Expect(subscription.Status.InstallPlanRef).ToNot(Equal(nil))
   310  		Expect(catalogCSV.GetName()).To(Equal(subscription.Status.CurrentCSV))
   311  
   312  		By("eventually the subscription should create a new install plan")
   313  		Eventually(func() bool {
   314  			sub, _ := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.TODO(), subscription.GetName(), metav1.GetOptions{})
   315  			GinkgoT().Logf("waiting for subscription %s to generate a new install plan...", subscription.GetName())
   316  			return sub.Status.InstallPlanRef.Name != oldInstallPlanRef
   317  		}, 5*time.Minute, 10*time.Second).Should(BeTrue())
   318  
   319  		By("eventually the new installplan should succeed")
   320  		Eventually(func() bool {
   321  			sub, _ := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.TODO(), subscription.GetName(), metav1.GetOptions{})
   322  			ip, err := crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Get(context.TODO(), sub.Status.InstallPlanRef.Name, metav1.GetOptions{})
   323  			if apierrors.IsNotFound(err) {
   324  				return false
   325  			}
   326  			GinkgoT().Logf("waiting for installplan to succeed...currently %s", ip.Status.Phase)
   327  			return ip.Status.Phase == operatorsv1alpha1.InstallPlanPhaseComplete
   328  		}).Should(BeTrue())
   329  		GinkgoT().Log("manually reconciled potentially unsafe CRD upgrade")
   330  	})
   331  
   332  	It("blocks a CRD upgrade that could cause data loss", func() {
   333  		By("checking the storage versions in the existing CRD status and the spec of the new CRD")
   334  
   335  		mainPackageName := genName("nginx-update2-")
   336  		mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName)
   337  		stableChannel := "stable"
   338  
   339  		crdPlural := genName("ins-")
   340  		crdName := crdPlural + ".cluster.com"
   341  		oldCRD := apiextensionsv1.CustomResourceDefinition{
   342  			ObjectMeta: metav1.ObjectMeta{
   343  				Name: crdName,
   344  			},
   345  			Spec: apiextensionsv1.CustomResourceDefinitionSpec{
   346  				Group: "cluster.com",
   347  				Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
   348  					{
   349  						Name:    "v1alpha2",
   350  						Served:  true,
   351  						Storage: true,
   352  						Schema: &apiextensionsv1.CustomResourceValidation{
   353  							OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
   354  								Type:        "object",
   355  								Description: "my crd schema",
   356  							},
   357  						},
   358  					},
   359  					{
   360  						Name:    "v2alpha1",
   361  						Served:  true,
   362  						Storage: false,
   363  						Schema: &apiextensionsv1.CustomResourceValidation{
   364  							OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
   365  								Type:        "object",
   366  								Description: "my crd schema",
   367  							},
   368  						},
   369  					},
   370  				},
   371  				Names: apiextensionsv1.CustomResourceDefinitionNames{
   372  					Plural:   crdPlural,
   373  					Singular: crdPlural,
   374  					Kind:     crdPlural,
   375  					ListKind: "list" + crdPlural,
   376  				},
   377  				Scope: apiextensionsv1.NamespaceScoped,
   378  			},
   379  		}
   380  
   381  		alphaChannel := "alpha"
   382  		mainPackageAlpha := fmt.Sprintf("%s-alpha", mainPackageName)
   383  		newCRD := apiextensionsv1.CustomResourceDefinition{
   384  			ObjectMeta: metav1.ObjectMeta{
   385  				Name: crdName,
   386  			},
   387  			Spec: apiextensionsv1.CustomResourceDefinitionSpec{
   388  				Group: "cluster.com",
   389  				Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
   390  					{
   391  						Name:    "v1alpha3",
   392  						Served:  true,
   393  						Storage: true,
   394  						Schema: &apiextensionsv1.CustomResourceValidation{
   395  							OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
   396  								Type:        "object",
   397  								Description: "my crd schema",
   398  							},
   399  						},
   400  					},
   401  					{
   402  						Name:    "v2alpha2",
   403  						Served:  true,
   404  						Storage: false,
   405  						Schema: &apiextensionsv1.CustomResourceValidation{
   406  							OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
   407  								Type:        "object",
   408  								Description: "my crd schema",
   409  							},
   410  						},
   411  					},
   412  				},
   413  				Names: apiextensionsv1.CustomResourceDefinitionNames{
   414  					Plural:   crdPlural,
   415  					Singular: crdPlural,
   416  					Kind:     crdPlural,
   417  					ListKind: "list" + crdPlural,
   418  				},
   419  				Scope: apiextensionsv1.NamespaceScoped,
   420  			},
   421  		}
   422  
   423  		oldCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{oldCRD}, nil, nil)
   424  		newCSV := newCSV(mainPackageAlpha, generatedNamespace.GetName(), mainPackageStable, semver.MustParse("0.1.1"), []apiextensionsv1.CustomResourceDefinition{newCRD}, nil, nil)
   425  		mainCatalogName := genName("mock-ocs-main-update2-")
   426  		mainManifests := []registry.PackageManifest{
   427  			{
   428  				PackageName: mainPackageName,
   429  				Channels: []registry.PackageChannel{
   430  					{Name: stableChannel, CurrentCSVName: mainPackageStable},
   431  					{Name: alphaChannel, CurrentCSVName: mainPackageAlpha},
   432  				},
   433  				DefaultChannelName: stableChannel,
   434  			},
   435  		}
   436  
   437  		By("Create the catalog sources")
   438  		_, cleanupMainCatalogSource := createInternalCatalogSource(c, crc, mainCatalogName, generatedNamespace.GetName(), mainManifests, []apiextensionsv1.CustomResourceDefinition{oldCRD, newCRD}, []operatorsv1alpha1.ClusterServiceVersion{oldCSV, newCSV})
   439  		defer cleanupMainCatalogSource()
   440  		defer func() {
   441  			_ = crc.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Delete(context.TODO(), oldCSV.GetName(), metav1.DeleteOptions{})
   442  			_ = crc.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Delete(context.TODO(), newCSV.GetName(), metav1.DeleteOptions{})
   443  			_ = c.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.TODO(), oldCRD.GetName(), metav1.DeleteOptions{})
   444  			_ = c.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.TODO(), newCRD.GetName(), metav1.DeleteOptions{})
   445  		}()
   446  
   447  		By("Attempt to get the catalog source before creating install plan")
   448  		_, err := fetchCatalogSourceOnStatus(crc, mainCatalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced())
   449  		Expect(err).ToNot(HaveOccurred())
   450  
   451  		subscriptionName := genName("sub-nginx-update2-")
   452  		subscriptionCleanup := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic)
   453  		defer subscriptionCleanup()
   454  
   455  		subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker())
   456  		Expect(err).ToNot(HaveOccurred())
   457  		Expect(subscription).ToNot(BeNil())
   458  		Expect(subscription.Status.InstallPlanRef).ToNot(Equal(nil))
   459  		Expect(oldCSV.GetName()).To(Equal(subscription.Status.CurrentCSV))
   460  
   461  		installPlanName := subscription.Status.InstallPlanRef.Name
   462  
   463  		By("Wait for InstallPlan to be status: Complete before checking resource presence")
   464  		fetchedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete))
   465  		Expect(err).ToNot(HaveOccurred())
   466  		GinkgoT().Logf("Install plan %s fetched with status %s", fetchedInstallPlan.GetName(), fetchedInstallPlan.Status.Phase)
   467  		Expect(fetchedInstallPlan.Status.Phase).To(Equal(operatorsv1alpha1.InstallPlanPhaseComplete))
   468  
   469  		By("old CRD has been installed onto the cluster - now upgrade the subscription to point to the channel with the new CRD")
   470  		By("installing the new CSV should fail with a warning about data loss, since a storage version is missing in the new CRD")
   471  		By("use server-side apply to apply the update to the subscription point to the alpha channel")
   472  		Eventually(Apply(subscription, func(s *operatorsv1alpha1.Subscription) error {
   473  			s.Spec.Channel = alphaChannel
   474  			return nil
   475  		})).Should(Succeed())
   476  		ctx.Ctx().Logf("updated subscription to point to alpha channel")
   477  
   478  		checker := subscriptionStateAtLatestChecker()
   479  		subscriptionAtLatestWithDifferentInstallPlan := func(v *operatorsv1alpha1.Subscription) bool {
   480  			return checker(v) && v.Status.InstallPlanRef != nil && v.Status.InstallPlanRef.Name != fetchedInstallPlan.Name
   481  		}
   482  
   483  		By("fetch new subscription")
   484  		s, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionAtLatestWithDifferentInstallPlan)
   485  		Expect(err).ToNot(HaveOccurred())
   486  		Expect(s).ToNot(BeNil())
   487  		Expect(s.Status.InstallPlanRef).ToNot(Equal(nil))
   488  
   489  		By("Check the error on the installplan - should be related to data loss and the CRD upgrade missing a stored version")
   490  		Eventually(func() (*operatorsv1alpha1.InstallPlan, error) {
   491  			return crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Get(context.TODO(), s.Status.InstallPlanRef.Name, metav1.GetOptions{})
   492  			// the install plan retry time out is 60 seconds, so we should expect the install plan to be failed within 2 minutes
   493  		}).Within(2 * time.Minute).Should(And(
   494  			WithTransform(
   495  				func(v *operatorsv1alpha1.InstallPlan) operatorsv1alpha1.InstallPlanPhase {
   496  					return v.Status.Phase
   497  				},
   498  				Equal(operatorsv1alpha1.InstallPlanPhaseFailed),
   499  			),
   500  			WithTransform(
   501  				func(v *operatorsv1alpha1.InstallPlan) string {
   502  					return v.Status.Conditions[len(v.Status.Conditions)-1].Message
   503  				},
   504  				ContainSubstring("risk of data loss"),
   505  			),
   506  		))
   507  	})
   508  })