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