From b873c798b91bb24dc02b4ce44b876f5c5b3d3297 Mon Sep 17 00:00:00 2001 From: Vu Dinh Date: Thu, 15 Aug 2019 09:59:07 -0400 Subject: [PATCH 1/2] Remove deprecated CRD's stored versions to allow CRD update Attempt to remove deprecated CRD versions from stored versions in current CRD. Then, the CRD update can be proceeded. Signed-off-by: Vu Dinh --- pkg/controller/operators/catalog/operator.go | 67 +++++++++++++++++--- 1 file changed, 59 insertions(+), 8 deletions(-) diff --git a/pkg/controller/operators/catalog/operator.go b/pkg/controller/operators/catalog/operator.go index 96678355d7..5cfce88816 100644 --- a/pkg/controller/operators/catalog/operator.go +++ b/pkg/controller/operators/catalog/operator.go @@ -1128,17 +1128,23 @@ func (o *Operator) ResolvePlan(plan *v1alpha1.InstallPlan) error { return nil } -// Ensure all existing served versions are present in new CRD -func ensureCRDVersions(oldCRD *v1beta1ext.CustomResourceDefinition, newCRD *v1beta1ext.CustomResourceDefinition) error { - newCRDVersions := map[string]struct{}{} +func getCRDVersionsMap(crd *v1beta1ext.CustomResourceDefinition) map[string]struct{} { + versionsMap := map[string]struct{}{} - for _, newVersion := range newCRD.Spec.Versions { - newCRDVersions[newVersion.Name] = struct{}{} + for _, version := range crd.Spec.Versions { + versionsMap[version.Name] = struct{}{} } - if newCRD.Spec.Version != "" { - newCRDVersions[newCRD.Spec.Version] = struct{}{} + if crd.Spec.Version != "" { + versionsMap[crd.Spec.Version] = struct{}{} } + return versionsMap +} + +// Ensure all existing served versions are present in new CRD +func ensureCRDVersions(oldCRD *v1beta1ext.CustomResourceDefinition, newCRD *v1beta1ext.CustomResourceDefinition) error { + newCRDVersions := getCRDVersionsMap(newCRD) + for _, oldVersion := range oldCRD.Spec.Versions { if oldVersion.Served { _, ok := newCRDVersions[oldVersion.Name] @@ -1203,10 +1209,39 @@ func (o *Operator) validateExistingCRs(gvr schema.GroupVersionResource, newCRD * return fmt.Errorf("error validating custom resource against new schema %#v: %s", newCRD.Spec.Validation, err) } } - return nil } +// Attempt to remove stored versions that have been deprecated before allowing +// those versions to be removed from the new CRD. +// The function may not always succeed as storedVersions requires at least one +// version. If there is only stored version, it won't be removed until a new +// stored version is added. +func removeDeprecatedStoredVersions(oldCRD *v1beta1ext.CustomResourceDefinition, newCRD *v1beta1ext.CustomResourceDefinition) *v1beta1ext.CustomResourceDefinition { + // StoredVersions requires to have at least one version. + if len(oldCRD.Status.StoredVersions) <= 1 { + return nil + } + + updatedCRD := oldCRD.DeepCopy() + + newCRDVersions := getCRDVersionsMap(newCRD) + newStoredVersions := []string{} + for _, v := range oldCRD.Status.StoredVersions { + _, ok := newCRDVersions[v] + if ok { + newStoredVersions = append(newStoredVersions, v) + } + } + + if len(newStoredVersions) < 1 { + return nil + } else { + updatedCRD.Status.StoredVersions = newStoredVersions + return updatedCRD + } +} + // ExecutePlan applies a planned InstallPlan to a namespace. func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error { if plan.Status.Phase != v1alpha1.InstallPlanPhaseInstalling { @@ -1268,6 +1303,14 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error { if len(matchedCSV) == 1 { o.logger.Debugf("Found one owner for CRD %v", crd) + crdStatus := removeDeprecatedStoredVersions(currentCRD, &crd) + if crdStatus != nil { + _, err = o.opClient.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().UpdateStatus(crdStatus) + if err != nil { + return errorwrap.Wrapf(err, "error updating CRD's status: %s", step.Resource.Name) + } + } + _, err = o.opClient.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().Update(&crd) if err != nil { return errorwrap.Wrapf(err, "error updating CRD: %s", step.Resource.Name) @@ -1284,6 +1327,14 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error { return errorwrap.Wrapf(err, "error validating existing CRs agains new CRD's schema: %s", step.Resource.Name) } + crdStatus := removeDeprecatedStoredVersions(currentCRD, &crd) + if crdStatus != nil { + _, err = o.opClient.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().UpdateStatus(crdStatus) + if err != nil { + return errorwrap.Wrapf(err, "error updating CRD's status: %s", step.Resource.Name) + } + } + _, err = o.opClient.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().Update(&crd) if err != nil { return errorwrap.Wrapf(err, "error update CRD: %s", step.Resource.Name) From 4bb852632382ec2eb153726940f0d597b0bd8c39 Mon Sep 17 00:00:00 2001 From: Vu Dinh Date: Tue, 20 Aug 2019 07:28:36 -0400 Subject: [PATCH 2/2] Add unit and e2e test cases for deprecating a CRD version Fix some minor issues in test suite. Signed-off-by: Vu Dinh --- pkg/controller/operators/catalog/operator.go | 50 ++-- .../operators/catalog/operator_test.go | 99 ++++++ test/e2e/installplan_e2e_test.go | 282 +++++++++++++++++- 3 files changed, 398 insertions(+), 33 deletions(-) diff --git a/pkg/controller/operators/catalog/operator.go b/pkg/controller/operators/catalog/operator.go index 5cfce88816..78c38a359e 100644 --- a/pkg/controller/operators/catalog/operator.go +++ b/pkg/controller/operators/catalog/operator.go @@ -1217,16 +1217,14 @@ func (o *Operator) validateExistingCRs(gvr schema.GroupVersionResource, newCRD * // The function may not always succeed as storedVersions requires at least one // version. If there is only stored version, it won't be removed until a new // stored version is added. -func removeDeprecatedStoredVersions(oldCRD *v1beta1ext.CustomResourceDefinition, newCRD *v1beta1ext.CustomResourceDefinition) *v1beta1ext.CustomResourceDefinition { +func removeDeprecatedStoredVersions(oldCRD *v1beta1ext.CustomResourceDefinition, newCRD *v1beta1ext.CustomResourceDefinition) []string { // StoredVersions requires to have at least one version. if len(oldCRD.Status.StoredVersions) <= 1 { return nil } - updatedCRD := oldCRD.DeepCopy() - - newCRDVersions := getCRDVersionsMap(newCRD) newStoredVersions := []string{} + newCRDVersions := getCRDVersionsMap(newCRD) for _, v := range oldCRD.Status.StoredVersions { _, ok := newCRDVersions[v] if ok { @@ -1237,8 +1235,7 @@ func removeDeprecatedStoredVersions(oldCRD *v1beta1ext.CustomResourceDefinition, if len(newStoredVersions) < 1 { return nil } else { - updatedCRD.Status.StoredVersions = newStoredVersions - return updatedCRD + return newStoredVersions } } @@ -1291,7 +1288,9 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error { if k8serrors.IsAlreadyExists(err) { currentCRD, _ := o.lister.APIExtensionsV1beta1().CustomResourceDefinitionLister().Get(crd.GetName()) // Compare 2 CRDs to see if it needs to be updatetd - if !reflect.DeepEqual(crd, *currentCRD) { + if !(reflect.DeepEqual(crd.Spec.Version, currentCRD.Spec.Version) && + reflect.DeepEqual(crd.Spec.Versions, currentCRD.Spec.Versions) && + reflect.DeepEqual(crd.Spec.Validation, currentCRD.Spec.Validation)) { // Verify CRD ownership, only attempt to update if // CRD has only one owner // Example: provided=database.coreos.com/v1alpha1/EtcdCluster @@ -1302,19 +1301,6 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error { crd.SetResourceVersion(currentCRD.GetResourceVersion()) if len(matchedCSV) == 1 { o.logger.Debugf("Found one owner for CRD %v", crd) - - crdStatus := removeDeprecatedStoredVersions(currentCRD, &crd) - if crdStatus != nil { - _, err = o.opClient.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().UpdateStatus(crdStatus) - if err != nil { - return errorwrap.Wrapf(err, "error updating CRD's status: %s", step.Resource.Name) - } - } - - _, err = o.opClient.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().Update(&crd) - if err != nil { - return errorwrap.Wrapf(err, "error updating CRD: %s", step.Resource.Name) - } } else if len(matchedCSV) > 1 { o.logger.Debugf("Found multiple owners for CRD %v", crd) @@ -1326,19 +1312,21 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error { if err = o.validateCustomResourceDefinition(currentCRD, &crd); err != nil { return errorwrap.Wrapf(err, "error validating existing CRs agains new CRD's schema: %s", step.Resource.Name) } - - crdStatus := removeDeprecatedStoredVersions(currentCRD, &crd) - if crdStatus != nil { - _, err = o.opClient.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().UpdateStatus(crdStatus) - if err != nil { - return errorwrap.Wrapf(err, "error updating CRD's status: %s", step.Resource.Name) - } - } - - _, err = o.opClient.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().Update(&crd) + } + // Remove deprecated version in CRD storedVersions + storeVersions := removeDeprecatedStoredVersions(currentCRD, &crd) + if storeVersions != nil { + currentCRD.Status.StoredVersions = storeVersions + resultCRD, err := o.opClient.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().UpdateStatus(currentCRD) if err != nil { - return errorwrap.Wrapf(err, "error update CRD: %s", step.Resource.Name) + return errorwrap.Wrapf(err, "error updating CRD's status: %s", step.Resource.Name) } + crd.SetResourceVersion(resultCRD.GetResourceVersion()) + } + // Update CRD to new version + _, err = o.opClient.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().Update(&crd) + if err != nil { + return errorwrap.Wrapf(err, "error updating CRD: %s", step.Resource.Name) } } // If it already existed, mark the step as Present. diff --git a/pkg/controller/operators/catalog/operator_test.go b/pkg/controller/operators/catalog/operator_test.go index 44eac8715e..9283a4b460 100644 --- a/pkg/controller/operators/catalog/operator_test.go +++ b/pkg/controller/operators/catalog/operator_test.go @@ -235,6 +235,105 @@ func TestEnsureCRDVersions(t *testing.T) { } } +func TestRemoveDeprecatedStoredVersions(t *testing.T) { + mainCRDPlural := "ins-main-test" + + currentVersions := []v1beta1.CustomResourceDefinitionVersion{ + { + Name: "v1alpha1", + Served: true, + Storage: false, + }, + { + Name: "v1alpha2", + Served: true, + Storage: true, + }, + } + + newVersions := []v1beta1.CustomResourceDefinitionVersion{ + { + Name: "v1alpha2", + Served: true, + Storage: false, + }, + { + Name: "v1beta1", + Served: true, + Storage: true, + }, + } + + crdStatusStoredVersions := v1beta1.CustomResourceDefinitionStatus{ + StoredVersions: []string{}, + } + + tests := []struct { + name string + oldCRD v1beta1.CustomResourceDefinition + newCRD v1beta1.CustomResourceDefinition + expectedResult []string + }{ + { + name: "only one stored version exists", + oldCRD: func() v1beta1.CustomResourceDefinition { + oldCRD := crd(mainCRDPlural) + oldCRD.Spec.Version = "" + oldCRD.Spec.Versions = currentVersions + oldCRD.Status = crdStatusStoredVersions + oldCRD.Status.StoredVersions = []string{"v1alpha1"} + return oldCRD + }(), + newCRD: func() v1beta1.CustomResourceDefinition { + newCRD := crd(mainCRDPlural) + newCRD.Spec.Version = "" + newCRD.Spec.Versions = newVersions + return newCRD + }(), + expectedResult: nil, + }, + { + name: "multiple stored versions with one deprecated version", + oldCRD: func() v1beta1.CustomResourceDefinition { + oldCRD := crd(mainCRDPlural) + oldCRD.Spec.Version = "" + oldCRD.Spec.Versions = currentVersions + oldCRD.Status.StoredVersions = []string{"v1alpha1", "v1alpha2"} + return oldCRD + }(), + newCRD: func() v1beta1.CustomResourceDefinition { + newCRD := crd(mainCRDPlural) + newCRD.Spec.Version = "" + newCRD.Spec.Versions = newVersions + return newCRD + }(), + expectedResult: []string{"v1alpha2"}, + }, + { + name: "multiple stored versions with all deprecated version", + oldCRD: func() v1beta1.CustomResourceDefinition { + oldCRD := crd(mainCRDPlural) + oldCRD.Spec.Version = "" + oldCRD.Spec.Versions = currentVersions + oldCRD.Status.StoredVersions = []string{"v1alpha1", "v1alpha3"} + return oldCRD + }(), + newCRD: func() v1beta1.CustomResourceDefinition { + newCRD := crd(mainCRDPlural) + newCRD.Spec.Version = "" + newCRD.Spec.Versions = newVersions + return newCRD + }(), + expectedResult: nil, + }, + } + + for _, tt := range tests { + resultCRD := removeDeprecatedStoredVersions(&tt.oldCRD, &tt.newCRD) + require.Equal(t, tt.expectedResult, resultCRD) + } +} + func TestExecutePlan(t *testing.T) { namespace := "ns" diff --git a/test/e2e/installplan_e2e_test.go b/test/e2e/installplan_e2e_test.go index e73e55cdde..0954abdf60 100644 --- a/test/e2e/installplan_e2e_test.go +++ b/test/e2e/installplan_e2e_test.go @@ -25,12 +25,32 @@ import ( "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" + "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient" "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil" opver "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/version" ) type checkInstallPlanFunc func(fip *v1alpha1.InstallPlan) bool +func validateCRDVersions(t *testing.T, c operatorclient.ClientInterface, name string, expectedVersions map[string]struct{}) { + // Retrieve CRD information + crd, err := c.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().Get(name, metav1.GetOptions{}) + require.NoError(t, err) + + require.Equal(t, len(expectedVersions), len(crd.Spec.Versions), "number of CRD versions don't not match installed") + + for _, version := range crd.Spec.Versions { + _, ok := expectedVersions[version.Name] + require.True(t, ok, "couldn't find %v in expected versions: %#v", version.Name, expectedVersions) + + // Remove the entry from the expected steps set (to ensure no duplicates in resolved plan) + delete(expectedVersions, version.Name) + } + + // Should have removed every matching version + require.Equal(t, 0, len(expectedVersions), "Actual CRD versions do not match expected") +} + func buildInstallPlanPhaseCheckFunc(phases ...v1alpha1.InstallPlanPhase) checkInstallPlanFunc { return func(fip *v1alpha1.InstallPlan) bool { satisfiesAny := false @@ -190,9 +210,20 @@ func newCSV(name, namespace, replaces string, version semver.Version, owned []ap // Populate owned and required for _, crd := range owned { + crdVersion := "v1alpha1" + if crd.Spec.Version != "" { + crdVersion = crd.Spec.Version + } else { + for _, v := range crd.Spec.Versions { + if v.Served && v.Storage { + crdVersion = v.Name + break + } + } + } desc := v1alpha1.CRDDescription{ Name: crd.GetName(), - Version: "v1alpha1", + Version: crdVersion, Kind: crd.Spec.Names.Plural, DisplayName: crd.GetName(), Description: crd.GetName(), @@ -201,9 +232,20 @@ func newCSV(name, namespace, replaces string, version semver.Version, owned []ap } for _, crd := range required { + crdVersion := "v1alpha1" + if crd.Spec.Version != "" { + crdVersion = crd.Spec.Version + } else { + for _, v := range crd.Spec.Versions { + if v.Served && v.Storage { + crdVersion = v.Name + break + } + } + } desc := v1alpha1.CRDDescription{ Name: crd.GetName(), - Version: "v1alpha1", + Version: crdVersion, Kind: crd.Spec.Names.Plural, DisplayName: crd.GetName(), Description: crd.GetName(), @@ -997,6 +1039,242 @@ func TestInstallPlanWithCRDSchemaChange(t *testing.T) { } } +func TestInstallPlanWithDeprecatedVersionCRD(t *testing.T) { + defer cleaner.NotifyTestComplete(t, true) + + // generated outside of the test table so that the same naming can be used for both old and new CSVs + mainCRDPlural := genName("ins") + + // excluded: new CRD, same version, same schema - won't trigger a CRD update + tests := []struct { + name string + expectedPhase v1alpha1.InstallPlanPhase + oldCRD *apiextensions.CustomResourceDefinition + intermediateCRD *apiextensions.CustomResourceDefinition + newCRD *apiextensions.CustomResourceDefinition + }{ + { + name: "upgrade CRD with deprecated version", + expectedPhase: v1alpha1.InstallPlanPhaseComplete, + oldCRD: func() *apiextensions.CustomResourceDefinition { + oldCRD := newCRD(mainCRDPlural) + oldCRD.Spec.Version = "v1alpha1" + oldCRD.Spec.Versions = []apiextensions.CustomResourceDefinitionVersion{ + { + Name: "v1alpha1", + Served: true, + Storage: true, + }, + } + return &oldCRD + }(), + intermediateCRD: func() *apiextensions.CustomResourceDefinition { + intermediateCRD := newCRD(mainCRDPlural) + intermediateCRD.Spec.Version = "v1alpha2" + intermediateCRD.Spec.Versions = []apiextensions.CustomResourceDefinitionVersion{ + { + Name: "v1alpha2", + Served: true, + Storage: true, + }, + { + Name: "v1alpha1", + Served: false, + Storage: false, + }, + } + return &intermediateCRD + }(), + newCRD: func() *apiextensions.CustomResourceDefinition { + newCRD := newCRD(mainCRDPlural) + newCRD.Spec.Version = "v1alpha2" + newCRD.Spec.Versions = []apiextensions.CustomResourceDefinitionVersion{ + { + Name: "v1alpha2", + Served: true, + Storage: true, + }, + { + Name: "v1beta1", + Served: true, + Storage: false, + }, + } + return &newCRD + }(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer cleaner.NotifyTestComplete(t, true) + + mainPackageName := genName("nginx-") + mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName) + mainPackageBeta := fmt.Sprintf("%s-beta", mainPackageName) + mainPackageDelta := fmt.Sprintf("%s-delta", mainPackageName) + + stableChannel := "stable" + betaChannel := "beta" + deltaChannel := "delta" + + // Create manifests + mainManifests := []registry.PackageManifest{ + { + PackageName: mainPackageName, + Channels: []registry.PackageChannel{ + {Name: stableChannel, CurrentCSVName: mainPackageStable}, + }, + DefaultChannelName: stableChannel, + }, + } + + // Create a new named install strategy + mainNamedStrategy := newNginxInstallStrategy(genName("dep-"), nil, nil) + + // Create new CSVs + mainStableCSV := newCSV(mainPackageStable, testNamespace, "", semver.MustParse("0.1.0"), []apiextensions.CustomResourceDefinition{*tt.oldCRD}, nil, mainNamedStrategy) + mainBetaCSV := newCSV(mainPackageBeta, testNamespace, mainPackageStable, semver.MustParse("0.2.0"), []apiextensions.CustomResourceDefinition{*tt.intermediateCRD}, nil, mainNamedStrategy) + mainDeltaCSV := newCSV(mainPackageDelta, testNamespace, mainPackageBeta, semver.MustParse("0.3.0"), []apiextensions.CustomResourceDefinition{*tt.newCRD}, nil, mainNamedStrategy) + + c := newKubeClient(t) + crc := newCRClient(t) + + // Create the catalog source + mainCatalogSourceName := genName("mock-ocs-main-") + _, cleanupCatalogSource := createInternalCatalogSource(t, c, crc, mainCatalogSourceName, testNamespace, mainManifests, []apiextensions.CustomResourceDefinition{*tt.oldCRD}, []v1alpha1.ClusterServiceVersion{mainStableCSV}) + defer cleanupCatalogSource() + + // Attempt to get the catalog source before creating install plan(s) + _, err := fetchCatalogSource(t, crc, mainCatalogSourceName, testNamespace, catalogSourceRegistryPodSynced) + require.NoError(t, err) + + subscriptionName := genName("sub-nginx-") + // this subscription will be cleaned up below without the clean up function + createSubscriptionForCatalog(t, crc, testNamespace, subscriptionName, mainCatalogSourceName, mainPackageName, stableChannel, "", v1alpha1.ApprovalAutomatic) + + subscription, err := fetchSubscription(t, crc, testNamespace, subscriptionName, subscriptionHasInstallPlanChecker) + require.NoError(t, err) + require.NotNil(t, subscription) + + installPlanName := subscription.Status.Install.Name + + // Wait for InstallPlan to be status: Complete or failed before checking resource presence + completeOrFailedFunc := buildInstallPlanPhaseCheckFunc(v1alpha1.InstallPlanPhaseComplete, v1alpha1.InstallPlanPhaseFailed) + fetchedInstallPlan, err := fetchInstallPlan(t, crc, installPlanName, completeOrFailedFunc) + require.NoError(t, err) + t.Logf("Install plan %s fetched with status %s", fetchedInstallPlan.GetName(), fetchedInstallPlan.Status.Phase) + require.Equal(t, v1alpha1.InstallPlanPhaseComplete, fetchedInstallPlan.Status.Phase) + + // Ensure CRD versions are accurate + expectedVersions := map[string]struct{}{ + "v1alpha1": {}, + } + + validateCRDVersions(t, c, tt.oldCRD.GetName(), expectedVersions) + + // Update the manifest + mainManifests = []registry.PackageManifest{ + { + PackageName: mainPackageName, + Channels: []registry.PackageChannel{ + {Name: stableChannel, CurrentCSVName: mainPackageStable}, + {Name: betaChannel, CurrentCSVName: mainPackageBeta}, + }, + DefaultChannelName: betaChannel, + }, + } + + updateInternalCatalog(t, c, crc, mainCatalogSourceName, testNamespace, []apiextensions.CustomResourceDefinition{*tt.intermediateCRD}, []v1alpha1.ClusterServiceVersion{mainStableCSV, mainBetaCSV}, mainManifests) + // Attempt to get the catalog source before creating install plan(s) + _, err = fetchCatalogSource(t, crc, mainCatalogSourceName, testNamespace, catalogSourceRegistryPodSynced) + require.NoError(t, err) + // Update the subscription resource to point to the beta CSV + err = crc.OperatorsV1alpha1().Subscriptions(testNamespace).DeleteCollection(metav1.NewDeleteOptions(0), metav1.ListOptions{}) + require.NoError(t, err) + + // existing cleanup should remove this + createSubscriptionForCatalog(t, crc, testNamespace, subscriptionName, mainCatalogSourceName, mainPackageName, betaChannel, "", v1alpha1.ApprovalAutomatic) + + subscription, err = fetchSubscription(t, crc, testNamespace, subscriptionName, subscriptionHasInstallPlanChecker) + require.NoError(t, err) + require.NotNil(t, subscription) + + installPlanName = subscription.Status.Install.Name + + // Wait for InstallPlan to be status: Complete or Failed before checking resource presence + fetchedInstallPlan, err = fetchInstallPlan(t, crc, installPlanName, buildInstallPlanPhaseCheckFunc(v1alpha1.InstallPlanPhaseComplete, v1alpha1.InstallPlanPhaseFailed)) + require.NoError(t, err) + t.Logf("Install plan %s fetched with status %s", fetchedInstallPlan.GetName(), fetchedInstallPlan.Status.Phase) + + require.Equal(t, tt.expectedPhase, fetchedInstallPlan.Status.Phase) + + // Ensure correct in-cluster resource(s) + fetchedCSV, err := fetchCSV(t, crc, mainBetaCSV.GetName(), testNamespace, csvSucceededChecker) + require.NoError(t, err) + + // Ensure CRD versions are accurate + expectedVersions = map[string]struct{}{ + "v1alpha1": {}, + "v1alpha2": {}, + } + + validateCRDVersions(t, c, tt.oldCRD.GetName(), expectedVersions) + + // Update the manifest + mainBetaCSV = newCSV(mainPackageBeta, testNamespace, "", semver.MustParse("0.2.0"), []apiextensions.CustomResourceDefinition{*tt.intermediateCRD}, nil, mainNamedStrategy) + mainManifests = []registry.PackageManifest{ + { + PackageName: mainPackageName, + Channels: []registry.PackageChannel{ + {Name: betaChannel, CurrentCSVName: mainPackageBeta}, + {Name: deltaChannel, CurrentCSVName: mainPackageDelta}, + }, + DefaultChannelName: deltaChannel, + }, + } + + updateInternalCatalog(t, c, crc, mainCatalogSourceName, testNamespace, []apiextensions.CustomResourceDefinition{*tt.newCRD}, []v1alpha1.ClusterServiceVersion{mainBetaCSV, mainDeltaCSV}, mainManifests) + // Attempt to get the catalog source before creating install plan(s) + _, err = fetchCatalogSource(t, crc, mainCatalogSourceName, testNamespace, catalogSourceRegistryPodSynced) + require.NoError(t, err) + // Update the subscription resource to point to the beta CSV + err = crc.OperatorsV1alpha1().Subscriptions(testNamespace).DeleteCollection(metav1.NewDeleteOptions(0), metav1.ListOptions{}) + require.NoError(t, err) + + // existing cleanup should remove this + createSubscriptionForCatalog(t, crc, testNamespace, subscriptionName, mainCatalogSourceName, mainPackageName, deltaChannel, "", v1alpha1.ApprovalAutomatic) + + subscription, err = fetchSubscription(t, crc, testNamespace, subscriptionName, subscriptionHasInstallPlanChecker) + require.NoError(t, err) + require.NotNil(t, subscription) + + installPlanName = subscription.Status.Install.Name + + // Wait for InstallPlan to be status: Complete or Failed before checking resource presence + fetchedInstallPlan, err = fetchInstallPlan(t, crc, installPlanName, buildInstallPlanPhaseCheckFunc(v1alpha1.InstallPlanPhaseComplete, v1alpha1.InstallPlanPhaseFailed)) + require.NoError(t, err) + t.Logf("Install plan %s fetched with status %s", fetchedInstallPlan.GetName(), fetchedInstallPlan.Status.Phase) + + require.Equal(t, tt.expectedPhase, fetchedInstallPlan.Status.Phase) + + // Ensure correct in-cluster resource(s) + fetchedCSV, err = fetchCSV(t, crc, mainDeltaCSV.GetName(), testNamespace, csvSucceededChecker) + require.NoError(t, err) + + // Ensure CRD versions are accurate + expectedVersions = map[string]struct{}{ + "v1alpha2": {}, + "v1beta1": {}, + } + + validateCRDVersions(t, c, tt.oldCRD.GetName(), expectedVersions) + + t.Logf("All expected resources resolved %s", fetchedCSV.Status.Phase) + }) + } +} + func TestUpdateCatalogForSubscription(t *testing.T) { defer cleaner.NotifyTestComplete(t, true)