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 })