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

     1  package e2e
     2  
     3  import (
     4  	"context"
     5  	"embed"
     6  	"fmt"
     7  	"path/filepath"
     8  
     9  	. "github.com/onsi/ginkgo/v2"
    10  	. "github.com/onsi/gomega"
    11  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
    12  	corev1 "k8s.io/api/core/v1"
    13  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    14  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    15  	"k8s.io/apimachinery/pkg/util/yaml"
    16  	"sigs.k8s.io/controller-runtime/pkg/client"
    17  
    18  	operatorsv1 "github.com/operator-framework/api/pkg/operators/v1"
    19  	operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
    20  	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
    21  	operatorsscheme "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned/scheme"
    22  	"github.com/operator-framework/operator-lifecycle-manager/test/e2e/ctx"
    23  )
    24  
    25  //go:embed testdata/fail-forward
    26  var testData embed.FS
    27  
    28  func loadData(versions ...string) ([]registry.PackageManifest, []operatorsv1alpha1.ClusterServiceVersion, error) {
    29  	var packageManifests []registry.PackageManifest
    30  	var clusterServiceVersions []operatorsv1alpha1.ClusterServiceVersion
    31  	for _, version := range versions {
    32  		packageManifest, err := loadPackageManifest(version)
    33  		if err != nil {
    34  			return nil, nil, err
    35  		}
    36  		packageManifests = append(packageManifests, *packageManifest)
    37  
    38  		clusterServiceVersion, err := loadClusterServiceVersion(version)
    39  		if err != nil {
    40  			return nil, nil, err
    41  		}
    42  		clusterServiceVersions = append(clusterServiceVersions, *clusterServiceVersion)
    43  	}
    44  
    45  	return packageManifests, clusterServiceVersions, nil
    46  }
    47  
    48  func loadPackageManifest(version string) (*registry.PackageManifest, error) {
    49  	raw, err := testData.ReadFile(filepath.Join("testdata", "fail-forward", version, "packagemanifest.yaml"))
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  
    54  	var packageManifest registry.PackageManifest
    55  	if err := yaml.Unmarshal(raw, &packageManifest); err != nil {
    56  		return nil, err
    57  	}
    58  
    59  	return &packageManifest, nil
    60  }
    61  
    62  func loadClusterServiceVersion(version string) (*operatorsv1alpha1.ClusterServiceVersion, error) {
    63  	raw, err := testData.ReadFile(filepath.Join("testdata", "fail-forward", version, "clusterserviceversion.yaml"))
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	var clusterServiceVersion operatorsv1alpha1.ClusterServiceVersion
    69  	gvk := operatorsv1alpha1.SchemeGroupVersion.WithKind("ClusterServiceVersion")
    70  	obj, gotGvk, err := operatorsscheme.Codecs.UniversalDeserializer().Decode(raw, &gvk, &clusterServiceVersion)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  	if gotGvk.String() != gvk.String() {
    75  		return nil, fmt.Errorf("expcted to unmarshal to %v, got %v", gvk.String(), gotGvk.String())
    76  	}
    77  
    78  	return obj.(*operatorsv1alpha1.ClusterServiceVersion), nil
    79  }
    80  
    81  func deployCatalogSource(namespace, name string, packages ...string) (func(), error) {
    82  	pms, csvs, err := loadData(packages...)
    83  	if err != nil {
    84  		return nil, fmt.Errorf("failed to load data: %w", err)
    85  	}
    86  	_, cleanup := createInternalCatalogSource(
    87  		ctx.Ctx().KubeClient(), ctx.Ctx().OperatorClient(),
    88  		name, namespace,
    89  		pms, []apiextensionsv1.CustomResourceDefinition{}, csvs,
    90  	)
    91  
    92  	_, err = fetchCatalogSourceOnStatus(ctx.Ctx().OperatorClient(), name, namespace, catalogSourceRegistryPodSynced())
    93  	if err != nil {
    94  		err = fmt.Errorf("failed to ensure registry pod synced: %w", err)
    95  	}
    96  	return cleanup, err
    97  }
    98  
    99  func updateCatalogSource(namespace, name string, packages ...string) (func(), error) {
   100  	By("removing the existing catalog source")
   101  	if err := ctx.Ctx().OperatorClient().OperatorsV1alpha1().CatalogSources(namespace).Delete(context.Background(), name, metav1.DeleteOptions{}); err != nil {
   102  		return nil, err
   103  	}
   104  
   105  	By("removing the previous catalog source pod(s)")
   106  	Eventually(func() (bool, error) {
   107  		listOpts := metav1.ListOptions{
   108  			LabelSelector: "olm.catalogSource=" + name,
   109  			FieldSelector: "status.phase=Running",
   110  		}
   111  		fetched, err := ctx.Ctx().KubeClient().KubernetesInterface().CoreV1().Pods(namespace).List(context.Background(), listOpts)
   112  		if err != nil {
   113  			return false, err
   114  		}
   115  		if len(fetched.Items) == 0 {
   116  			return true, nil
   117  		}
   118  		ctx.Ctx().Logf("waiting for the catalog source %s pod to be deleted...", fetched.Items[0].GetName())
   119  		return false, nil
   120  	}).Should(BeTrue())
   121  
   122  	By("updating the catalog with a new bundle images")
   123  	return deployCatalogSource(namespace, name, packages...)
   124  }
   125  
   126  var _ = Describe("Fail Forward Upgrades", func() {
   127  
   128  	var (
   129  		generatedNamespace corev1.Namespace
   130  		crclient           versioned.Interface
   131  		c                  client.Client
   132  	)
   133  
   134  	BeforeEach(func() {
   135  		crclient = newCRClient()
   136  		c = ctx.Ctx().Client()
   137  
   138  		By("creating the testing namespace with an OG that enabled fail forward behavior")
   139  		namespaceName := genName("fail-forward-e2e-")
   140  		og := operatorsv1.OperatorGroup{
   141  			ObjectMeta: metav1.ObjectMeta{
   142  				Name:      fmt.Sprintf("%s-operatorgroup", namespaceName),
   143  				Namespace: namespaceName,
   144  			},
   145  			Spec: operatorsv1.OperatorGroupSpec{
   146  				UpgradeStrategy: operatorsv1.UpgradeStrategyUnsafeFailForward,
   147  			},
   148  		}
   149  		generatedNamespace = SetupGeneratedTestNamespaceWithOperatorGroup(namespaceName, og)
   150  	})
   151  
   152  	AfterEach(func() {
   153  		By("deleting the testing namespace")
   154  		TeardownNamespace(generatedNamespace.GetName())
   155  	})
   156  
   157  	When("an InstallPlan is reporting a failed state", func() {
   158  
   159  		var (
   160  			catalogSourceName      string
   161  			subscription           *operatorsv1alpha1.Subscription
   162  			originalInstallPlanRef *corev1.ObjectReference
   163  			failedInstallPlanRef   *corev1.ObjectReference
   164  			cleanups               []func()
   165  		)
   166  
   167  		BeforeEach(func() {
   168  			By("deploying the testing catalog")
   169  			catalogSourceName = genName("mc-ip-failed-")
   170  			cleanup, deployError := deployCatalogSource(generatedNamespace.GetName(), catalogSourceName, "v0.1.0")
   171  			Expect(deployError).To(BeNil())
   172  			if cleanup != nil {
   173  				cleanups = append(cleanups, cleanup)
   174  			}
   175  
   176  			By("creating the testing subscription")
   177  			subscription = &operatorsv1alpha1.Subscription{
   178  				ObjectMeta: metav1.ObjectMeta{
   179  					Name:      fmt.Sprintf("%s-sub", catalogSourceName),
   180  					Namespace: generatedNamespace.GetName(),
   181  				},
   182  				Spec: &operatorsv1alpha1.SubscriptionSpec{
   183  					CatalogSource:          catalogSourceName,
   184  					CatalogSourceNamespace: generatedNamespace.GetName(),
   185  					Channel:                "stable",
   186  					Package:                "test-package",
   187  				},
   188  			}
   189  			Expect(c.Create(context.Background(), subscription)).To(BeNil())
   190  
   191  			By("waiting until the subscription has an IP reference")
   192  			subscription, err := fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasInstallPlanChecker())
   193  			Expect(err).Should(BeNil())
   194  
   195  			originalInstallPlanRef = subscription.Status.InstallPlanRef
   196  
   197  			By("waiting for the v0.1.0 CSV to report a succeeded phase")
   198  			_, err = fetchCSV(crclient, generatedNamespace.GetName(), subscription.Status.CurrentCSV, buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded))
   199  			Expect(err).ShouldNot(HaveOccurred())
   200  
   201  			By("updating the catalog with a v0.2.0 bundle that has an invalid CSV")
   202  			cleanup, deployError = updateCatalogSource(generatedNamespace.GetName(), catalogSourceName, "v0.1.0", "v0.2.0-invalid-csv")
   203  			Expect(deployError).To(BeNil())
   204  			if cleanup != nil {
   205  				cleanups = append(cleanups, cleanup)
   206  			}
   207  
   208  			By("verifying the subscription is referencing a new installplan")
   209  			subscription, err = fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasInstallPlanDifferentChecker(originalInstallPlanRef.Name))
   210  			Expect(err).Should(BeNil())
   211  
   212  			By("waiting for the bad InstallPlan to report a failed installation state")
   213  			failedInstallPlanRef = subscription.Status.InstallPlanRef
   214  			_, err = fetchInstallPlan(GinkgoT(), crclient, failedInstallPlanRef.Name, failedInstallPlanRef.Namespace, buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseFailed))
   215  			Expect(err).To(BeNil())
   216  		})
   217  		AfterEach(func() {
   218  			By("removing the testing catalog resources")
   219  			for _, cleanup := range cleanups {
   220  				cleanup()
   221  			}
   222  		})
   223  		It("eventually reports a successful state when multiple bad versions are rolled forward", func() {
   224  			By("updating the catalog with a v0.2.1 bundle that has an invalid CSV")
   225  			cleanup, deployError := updateCatalogSource(generatedNamespace.GetName(), catalogSourceName, "v0.1.0", "v0.2.0-invalid-csv", "v0.2.1-invalid-csv")
   226  			Expect(deployError).To(BeNil())
   227  			if cleanup != nil {
   228  				cleanups = append(cleanups, cleanup)
   229  			}
   230  
   231  			By("waiting for the subscription to have the example-operator.v0.2.1&invalid status.currentCSV")
   232  			_, err := fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasCurrentCSV("example-operator.v0.2.1&invalid"))
   233  			Expect(err).Should(BeNil())
   234  
   235  			By("verifying the subscription is referencing a new InstallPlan")
   236  			subscription, err = fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasInstallPlanDifferentChecker(originalInstallPlanRef.Name))
   237  			Expect(err).Should(BeNil())
   238  
   239  			By("waiting for the v0.2.1 InstallPlan to report a failed state")
   240  			ref := subscription.Status.InstallPlanRef
   241  			_, err = fetchInstallPlan(GinkgoT(), crclient, ref.Name, ref.Namespace, buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseFailed))
   242  			Expect(err).To(BeNil())
   243  
   244  			By("updating the catalog with a fixed v0.3.0 bundle")
   245  			cleanup, deployError = updateCatalogSource(generatedNamespace.GetName(), catalogSourceName, "v0.1.0", "v0.2.0-invalid-csv", "v0.2.1-invalid-csv", "v0.3.0-skips")
   246  			Expect(deployError).To(BeNil())
   247  			if cleanup != nil {
   248  				cleanups = append(cleanups, cleanup)
   249  			}
   250  
   251  			By("waiting for the subscription to have the example-operator.v0.3.0 status.currentCSV")
   252  			_, err = fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasCurrentCSV("example-operator.v0.3.0"))
   253  			Expect(err).Should(BeNil())
   254  
   255  			By("verifying the subscription is referencing a new InstallPlan")
   256  			subscription, err = fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasInstallPlanDifferentChecker(originalInstallPlanRef.Name))
   257  			Expect(err).Should(BeNil())
   258  
   259  			By("waiting for the fixed v0.3.0 InstallPlan to report a successful state")
   260  			ref = subscription.Status.InstallPlanRef
   261  			_, err = fetchInstallPlan(GinkgoT(), crclient, ref.Name, ref.Namespace, buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete))
   262  			Expect(err).To(BeNil())
   263  		})
   264  
   265  		It("[FLAKE] eventually reports a successful state when using skip ranges", func() {
   266  			By("updating the catalog with a fixed v0.3.0 bundle")
   267  			cleanup, deployError := updateCatalogSource(generatedNamespace.GetName(), catalogSourceName, "v0.1.0", "v0.2.0-invalid-csv", "v0.3.0-skip-range")
   268  			Expect(deployError).To(BeNil())
   269  			if cleanup != nil {
   270  				cleanups = append(cleanups, cleanup)
   271  			}
   272  
   273  			By("waiting for the subscription to have the example-operator.v0.3.0 status.currentCSV")
   274  			_, err := fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasCurrentCSV("example-operator.v0.3.0"))
   275  			Expect(err).Should(BeNil())
   276  
   277  			By("verifying the subscription is referencing a new InstallPlan")
   278  			subscription, err = fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasInstallPlanDifferentChecker(originalInstallPlanRef.Name))
   279  			Expect(err).Should(BeNil())
   280  
   281  			By("waiting for the fixed v0.3.0 InstallPlan to report a successful state")
   282  			ref := subscription.Status.InstallPlanRef
   283  			_, err = fetchInstallPlan(GinkgoT(), crclient, ref.Name, ref.Namespace, buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete))
   284  			Expect(err).To(BeNil())
   285  		})
   286  		It("eventually reports a successful state when using skips", func() {
   287  			By("updating the catalog with a fixed v0.3.0 bundle")
   288  			cleanup, deployError := updateCatalogSource(generatedNamespace.GetName(), catalogSourceName, "v0.1.0", "v0.2.0-invalid-csv", "v0.3.0-skips")
   289  			Expect(deployError).To(BeNil())
   290  			if cleanup != nil {
   291  				cleanups = append(cleanups, cleanup)
   292  			}
   293  
   294  			By("waiting for the subscription to have the example-operator.v0.3.0 status.currentCSV")
   295  			_, err := fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasCurrentCSV("example-operator.v0.3.0"))
   296  			Expect(err).Should(BeNil())
   297  
   298  			By("verifying the subscription is referencing a new InstallPlan")
   299  			subscription, err = fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasInstallPlanDifferentChecker(originalInstallPlanRef.Name))
   300  			Expect(err).Should(BeNil())
   301  
   302  			By("waiting for the fixed v0.3.0 InstallPlan to report a successful state")
   303  			ref := subscription.Status.InstallPlanRef
   304  			_, err = fetchInstallPlan(GinkgoT(), crclient, ref.Name, ref.Namespace, buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete))
   305  			Expect(err).To(BeNil())
   306  		})
   307  		It("eventually reports a failed state when using replaces", func() {
   308  			By("updating the catalog with a fixed v0.3.0 bundle")
   309  			cleanup, deployError := updateCatalogSource(generatedNamespace.GetName(), catalogSourceName, "v0.1.0", "v0.2.0-invalid-csv", "v0.3.0-replaces-invalid-csv")
   310  			Expect(deployError).To(BeNil())
   311  			if cleanup != nil {
   312  				cleanups = append(cleanups, cleanup)
   313  			}
   314  
   315  			By("waiting for the subscription to maintain the example-operator.v0.2.0&invalid status.currentCSV")
   316  			Consistently(subscriptionCurrentCSVGetter(crclient, subscription.GetNamespace(), subscription.GetName())).Should(Equal("example-operator.v0.2.0&invalid"))
   317  
   318  			By("verifying the subscription is referencing the same InstallPlan")
   319  			subscription, err := fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasInstallPlanChecker())
   320  			Expect(err).Should(BeNil())
   321  			Expect(subscription.Status.InstallPlanRef.Name).To(Equal(failedInstallPlanRef.Name))
   322  		})
   323  	})
   324  	When("a CSV resource is in a failed state", func() {
   325  
   326  		var (
   327  			catalogSourceName string
   328  			subscription      *operatorsv1alpha1.Subscription
   329  			cleanups          []func()
   330  		)
   331  
   332  		BeforeEach(func() {
   333  			By("deploying the testing catalog")
   334  			catalogSourceName = genName("mc-csv-failed-")
   335  			cleanup, deployError := deployCatalogSource(generatedNamespace.GetName(), catalogSourceName, "v0.1.0")
   336  			Expect(deployError).To(BeNil())
   337  			if cleanup != nil {
   338  				cleanups = append(cleanups, cleanup)
   339  			}
   340  
   341  			By("creating the testing subscription")
   342  			subscription = &operatorsv1alpha1.Subscription{
   343  				ObjectMeta: metav1.ObjectMeta{
   344  					Name:      fmt.Sprintf("%s-sub", catalogSourceName),
   345  					Namespace: generatedNamespace.GetName(),
   346  				},
   347  				Spec: &operatorsv1alpha1.SubscriptionSpec{
   348  					CatalogSource:          catalogSourceName,
   349  					CatalogSourceNamespace: generatedNamespace.GetName(),
   350  					Channel:                "stable",
   351  					Package:                "test-package",
   352  				},
   353  			}
   354  			Expect(c.Create(context.Background(), subscription)).To(BeNil())
   355  
   356  			By("waiting until the subscription has an IP reference")
   357  			subscription, err := fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasInstallPlanChecker())
   358  			Expect(err).Should(BeNil())
   359  
   360  			By("waiting for the v0.1.0 CSV to report a succeeded phase")
   361  			_, err = fetchCSV(crclient, generatedNamespace.GetName(), subscription.Status.CurrentCSV, buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded))
   362  			Expect(err).ShouldNot(HaveOccurred())
   363  
   364  			By("updating the catalog with a broken v0.2.0 csv")
   365  			cleanup, deployError = updateCatalogSource(generatedNamespace.GetName(), catalogSourceName, "v0.1.0", "v0.2.0-invalid-deployment")
   366  			Expect(deployError).To(BeNil())
   367  			if cleanup != nil {
   368  				cleanups = append(cleanups, cleanup)
   369  			}
   370  
   371  			badCSV := "example-operator.v0.2.0"
   372  			By("verifying the subscription has installed the current csv")
   373  			subscription, err = fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasCurrentCSV(badCSV))
   374  			Expect(err).Should(BeNil())
   375  
   376  			By("waiting for the bad CSV to report a failed state")
   377  			_, err = fetchCSV(crclient, generatedNamespace.GetName(), subscription.Status.CurrentCSV, csvFailedChecker)
   378  			Expect(err).To(BeNil())
   379  
   380  		})
   381  
   382  		AfterEach(func() {
   383  			By("removing the testing catalog resources")
   384  			for _, cleanup := range cleanups {
   385  				cleanup()
   386  			}
   387  		})
   388  
   389  		It("eventually reports a successful state when using skip ranges", func() {
   390  			By("patching the catalog with a fixed version")
   391  			cleanup, deployError := updateCatalogSource(generatedNamespace.GetName(), catalogSourceName, "v0.1.0", "v0.2.0-invalid-deployment", "v0.3.0-skip-range")
   392  			Expect(deployError).To(BeNil())
   393  			if cleanup != nil {
   394  				cleanups = append(cleanups, cleanup)
   395  			}
   396  
   397  			By("waiting for the subscription to have the example-operator.v0.3.0 status.currentCSV")
   398  			_, err := fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasCurrentCSV("example-operator.v0.3.0"))
   399  			Expect(err).Should(BeNil())
   400  		})
   401  
   402  		It("eventually reports a successful state when using skips", func() {
   403  			By("patching the catalog with a fixed version")
   404  			cleanup, deployError := updateCatalogSource(generatedNamespace.GetName(), catalogSourceName, "v0.1.0", "v0.2.0-invalid-deployment", "v0.3.0-skips")
   405  			Expect(deployError).To(BeNil())
   406  			if cleanup != nil {
   407  				cleanups = append(cleanups, cleanup)
   408  			}
   409  
   410  			By("waiting for the subscription to have the example-operator.v0.3.0 status.currentCSV")
   411  			_, err := fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasCurrentCSV("example-operator.v0.3.0"))
   412  			Expect(err).Should(BeNil())
   413  		})
   414  
   415  		It("[FLAKE] eventually reports a successful state when using replaces", func() {
   416  			By("patching the catalog with a fixed version")
   417  			cleanup, deployError := updateCatalogSource(generatedNamespace.GetName(), catalogSourceName, "v0.1.0", "v0.2.0-invalid-deployment", "v0.3.0-replaces-invalid-deployment")
   418  			Expect(deployError).To(BeNil())
   419  			if cleanup != nil {
   420  				cleanups = append(cleanups, cleanup)
   421  			}
   422  
   423  			By("waiting for the subscription to have the example-operator.v0.3.0 status.currentCSV")
   424  			_, err := fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasCurrentCSV("example-operator.v0.3.0"))
   425  			Expect(err).Should(BeNil())
   426  		})
   427  	})
   428  })