github.com/operator-framework/operator-lifecycle-manager@v0.30.0/test/e2e/subscription_e2e_test.go (about) 1 package e2e 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "os" 8 "path/filepath" 9 "reflect" 10 "strings" 11 "sync" 12 "time" 13 14 "github.com/blang/semver/v4" 15 "github.com/ghodss/yaml" 16 . "github.com/onsi/ginkgo/v2" 17 . "github.com/onsi/gomega" 18 configv1 "github.com/openshift/api/config/v1" 19 configv1client "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1" 20 "github.com/stretchr/testify/require" 21 appsv1 "k8s.io/api/apps/v1" 22 corev1 "k8s.io/api/core/v1" 23 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 24 "k8s.io/apimachinery/pkg/api/equality" 25 apierrors "k8s.io/apimachinery/pkg/api/errors" 26 "k8s.io/apimachinery/pkg/api/resource" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/types" 29 "k8s.io/apimachinery/pkg/util/wait" 30 "k8s.io/client-go/discovery" 31 "sigs.k8s.io/controller-runtime/pkg/client" 32 33 "github.com/operator-framework/api/pkg/lib/version" 34 operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" 35 operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" 36 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" 37 "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install" 38 "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" 39 "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/projection" 40 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient" 41 "github.com/operator-framework/operator-lifecycle-manager/test/e2e/ctx" 42 registryapi "github.com/operator-framework/operator-registry/pkg/api" 43 ) 44 45 func Step(level int, text string, callbacks ...func()) { 46 By(strings.Repeat(" ", level*2)+text, callbacks...) 47 } 48 49 const ( 50 timeout = time.Second * 20 51 interval = time.Millisecond * 100 52 ) 53 54 const ( 55 subscriptionTestDataBaseDir = "subscription/" 56 ) 57 58 var _ = Describe("Subscription", func() { 59 var ( 60 generatedNamespace corev1.Namespace 61 operatorGroup operatorsv1.OperatorGroup 62 c operatorclient.ClientInterface 63 crc versioned.Interface 64 ) 65 66 BeforeEach(func() { 67 c = ctx.Ctx().KubeClient() 68 crc = ctx.Ctx().OperatorClient() 69 70 nsName := genName("subscription-e2e-") 71 operatorGroup = operatorsv1.OperatorGroup{ 72 ObjectMeta: metav1.ObjectMeta{ 73 Name: fmt.Sprintf("%s-operatorgroup", nsName), 74 Namespace: nsName, 75 }, 76 } 77 generatedNamespace = SetupGeneratedTestNamespaceWithOperatorGroup(nsName, operatorGroup) 78 }) 79 80 AfterEach(func() { 81 TeardownNamespace(generatedNamespace.GetName()) 82 }) 83 84 When("an entry in the middle of a channel does not provide a required GVK", func() { 85 var ( 86 teardown func() 87 ) 88 89 BeforeEach(func() { 90 teardown = func() {} 91 packages := []registry.PackageManifest{ 92 { 93 PackageName: "dependency", 94 Channels: []registry.PackageChannel{ 95 {Name: "channel-dependency", CurrentCSVName: "csv-dependency-3"}, 96 }, 97 DefaultChannelName: "channel-dependency", 98 }, 99 { 100 PackageName: "root", 101 Channels: []registry.PackageChannel{ 102 {Name: "channel-root", CurrentCSVName: "csv-root"}, 103 }, 104 DefaultChannelName: "channel-root", 105 }, 106 } 107 108 crds := []apiextensionsv1.CustomResourceDefinition{newCRD(genName("crd-"))} 109 csvs := []operatorsv1alpha1.ClusterServiceVersion{ 110 newCSV("csv-dependency-1", generatedNamespace.GetName(), "", semver.MustParse("1.0.0"), crds, nil, nil), 111 newCSV("csv-dependency-2", generatedNamespace.GetName(), "csv-dependency-1", semver.MustParse("2.0.0"), nil, nil, nil), 112 newCSV("csv-dependency-3", generatedNamespace.GetName(), "csv-dependency-2", semver.MustParse("3.0.0"), crds, nil, nil), 113 newCSV("csv-root", generatedNamespace.GetName(), "", semver.MustParse("1.0.0"), nil, crds, nil), 114 } 115 116 _, teardown = createInternalCatalogSource(ctx.Ctx().KubeClient(), ctx.Ctx().OperatorClient(), "test-catalog", generatedNamespace.GetName(), packages, crds, csvs) 117 _, err := fetchCatalogSourceOnStatus(ctx.Ctx().OperatorClient(), "test-catalog", generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 118 Expect(err).NotTo(HaveOccurred()) 119 120 createSubscriptionForCatalog(ctx.Ctx().OperatorClient(), generatedNamespace.GetName(), "test-subscription", "test-catalog", "root", "channel-root", "", operatorsv1alpha1.ApprovalAutomatic) 121 }) 122 123 AfterEach(func() { 124 teardown() 125 }) 126 127 It("should create a Subscription for the latest entry providing the required GVK", func() { 128 Eventually(func() ([]operatorsv1alpha1.Subscription, error) { 129 var list operatorsv1alpha1.SubscriptionList 130 if err := ctx.Ctx().Client().List(context.Background(), &list); err != nil { 131 return nil, err 132 } 133 return list.Items, nil 134 }).Should(ContainElement(WithTransform( 135 func(in operatorsv1alpha1.Subscription) operatorsv1alpha1.SubscriptionSpec { 136 return operatorsv1alpha1.SubscriptionSpec{ 137 CatalogSource: in.Spec.CatalogSource, 138 CatalogSourceNamespace: in.Spec.CatalogSourceNamespace, 139 Package: in.Spec.Package, 140 Channel: in.Spec.Channel, 141 StartingCSV: in.Spec.StartingCSV, 142 } 143 }, 144 Equal(operatorsv1alpha1.SubscriptionSpec{ 145 CatalogSource: "test-catalog", 146 CatalogSourceNamespace: generatedNamespace.GetName(), 147 Package: "dependency", 148 Channel: "channel-dependency", 149 StartingCSV: "csv-dependency-3", 150 }), 151 ))) 152 }) 153 }) 154 155 It("creation if not installed", func() { 156 By(` I. Creating a new subscription`) 157 By(` A. If package is not installed, creating a subscription should install latest version`) 158 159 defer func() { 160 if env := os.Getenv("SKIP_CLEANUP"); env != "" { 161 fmt.Printf("Skipping cleanup of subscriptions in namespace %s\n", generatedNamespace.GetName()) 162 return 163 } 164 require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{})) 165 }() 166 167 By("creating a catalog") 168 require.NoError(GinkgoT(), initCatalog(GinkgoT(), generatedNamespace.GetName(), c, crc)) 169 170 By(fmt.Sprintf("creating a subscription: %s/%s", generatedNamespace.GetName(), testSubscriptionName)) 171 cleanup, _ := createSubscription(GinkgoT(), crc, generatedNamespace.GetName(), testSubscriptionName, testPackageName, betaChannel, operatorsv1alpha1.ApprovalAutomatic) 172 173 defer cleanup() 174 175 By("waiting for the subscription to have a current CSV and be at latest") 176 var currentCSV string 177 Eventually(func() bool { 178 fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), testSubscriptionName, metav1.GetOptions{}) 179 if err != nil { 180 return false 181 } 182 if fetched != nil { 183 currentCSV = fetched.Status.CurrentCSV 184 return fetched.Status.State == operatorsv1alpha1.SubscriptionStateAtLatest 185 } 186 return false 187 }, 5*time.Minute, 10*time.Second).Should(BeTrue()) 188 189 csv, err := fetchCSV(crc, generatedNamespace.GetName(), currentCSV, buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded)) 190 require.NoError(GinkgoT(), err) 191 192 By(`Check for the olm.package property as a proxy for`) 193 By(`verifying that the annotation value is reasonable.`) 194 Expect( 195 projection.PropertyListFromPropertiesAnnotation(csv.GetAnnotations()["operatorframework.io/properties"]), 196 ).To(ContainElement( 197 ®istryapi.Property{Type: "olm.package", Value: `{"packageName":"myapp","version":"0.1.1"}`}, 198 )) 199 }) 200 201 It("creation using existing CSV", func() { 202 By(` I. Creating a new subscription`) 203 By(` B. If package is already installed, creating a subscription should upgrade it to the latest version`) 204 205 defer func() { 206 require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{})) 207 }() 208 require.NoError(GinkgoT(), initCatalog(GinkgoT(), generatedNamespace.GetName(), c, crc)) 209 210 By(`Will be cleaned up by the upgrade process`) 211 _, err := createCSV(c, crc, stableCSV, generatedNamespace.GetName(), false, false) 212 require.NoError(GinkgoT(), err) 213 214 subscriptionCleanup, _ := createSubscription(GinkgoT(), crc, generatedNamespace.GetName(), testSubscriptionName, testPackageName, alphaChannel, operatorsv1alpha1.ApprovalAutomatic) 215 defer subscriptionCleanup() 216 217 subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), testSubscriptionName, subscriptionStateAtLatestChecker()) 218 require.NoError(GinkgoT(), err) 219 require.NotNil(GinkgoT(), subscription) 220 _, err = fetchCSV(crc, generatedNamespace.GetName(), subscription.Status.CurrentCSV, buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded)) 221 require.NoError(GinkgoT(), err) 222 }) 223 It("skip range", func() { 224 225 crdPlural := genName("ins") 226 crdName := crdPlural + ".cluster.com" 227 crd := apiextensionsv1.CustomResourceDefinition{ 228 ObjectMeta: metav1.ObjectMeta{ 229 Name: crdName, 230 }, 231 Spec: apiextensionsv1.CustomResourceDefinitionSpec{ 232 Group: "cluster.com", 233 Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ 234 { 235 Name: "v1alpha1", 236 Served: true, 237 Storage: true, 238 Schema: &apiextensionsv1.CustomResourceValidation{ 239 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 240 Type: "object", 241 Description: "my crd schema", 242 }, 243 }, 244 }, 245 }, 246 Names: apiextensionsv1.CustomResourceDefinitionNames{ 247 Plural: crdPlural, 248 Singular: crdPlural, 249 Kind: crdPlural, 250 ListKind: "list" + crdPlural, 251 }, 252 Scope: apiextensionsv1.NamespaceScoped, 253 }, 254 } 255 256 mainPackageName := genName("nginx-") 257 mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName) 258 updatedPackageStable := fmt.Sprintf("%s-updated", mainPackageName) 259 stableChannel := "stable" 260 mainCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0-1556661347"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) 261 updatedCSV := newCSV(updatedPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0-1556661832"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) 262 updatedCSV.SetAnnotations(map[string]string{"olm.skipRange": ">=0.1.0-1556661347 <0.1.0-1556661832"}) 263 264 c := newKubeClient() 265 crc := newCRClient() 266 defer func() { 267 require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{})) 268 }() 269 270 mainCatalogName := genName("mock-ocs-main-") 271 272 By(`Create separate manifests for each CatalogSource`) 273 mainManifests := []registry.PackageManifest{ 274 { 275 PackageName: mainPackageName, 276 Channels: []registry.PackageChannel{ 277 {Name: stableChannel, CurrentCSVName: mainPackageStable}, 278 }, 279 DefaultChannelName: stableChannel, 280 }, 281 } 282 updatedManifests := []registry.PackageManifest{ 283 { 284 PackageName: mainPackageName, 285 Channels: []registry.PackageChannel{ 286 {Name: stableChannel, CurrentCSVName: updatedPackageStable}, 287 }, 288 DefaultChannelName: stableChannel, 289 }, 290 } 291 292 By(`Create catalog source`) 293 _, cleanupMainCatalogSource := createInternalCatalogSource(c, crc, mainCatalogName, generatedNamespace.GetName(), mainManifests, []apiextensionsv1.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{mainCSV}) 294 defer cleanupMainCatalogSource() 295 By(`Attempt to get the catalog source before creating subscription`) 296 _, err := fetchCatalogSourceOnStatus(crc, mainCatalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 297 require.NoError(GinkgoT(), err) 298 299 By(`Create a subscription`) 300 subscriptionName := genName("sub-nginx-") 301 subscriptionCleanup := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) 302 defer subscriptionCleanup() 303 304 By(`Wait for csv to install`) 305 firstCSV, err := fetchCSV(crc, generatedNamespace.GetName(), mainCSV.GetName(), csvSucceededChecker) 306 require.NoError(GinkgoT(), err) 307 308 By(`Update catalog with a new csv in the channel with a skip range`) 309 updateInternalCatalog(GinkgoT(), c, crc, mainCatalogName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{updatedCSV}, updatedManifests) 310 311 By(`Wait for csv to update`) 312 finalCSV, err := fetchCSV(crc, generatedNamespace.GetName(), updatedCSV.GetName(), csvSucceededChecker) 313 require.NoError(GinkgoT(), err) 314 315 By(`Ensure we set the replacement field based on the registry data`) 316 require.Equal(GinkgoT(), firstCSV.GetName(), finalCSV.Spec.Replaces) 317 }) 318 319 It("creation manual approval", func() { 320 By(`If installPlanApproval is set to manual, the installplans created should be created with approval: manual`) 321 322 defer func() { 323 require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{})) 324 }() 325 require.NoError(GinkgoT(), initCatalog(GinkgoT(), generatedNamespace.GetName(), c, crc)) 326 327 subscriptionCleanup, _ := createSubscription(GinkgoT(), crc, generatedNamespace.GetName(), "manual-subscription", testPackageName, stableChannel, operatorsv1alpha1.ApprovalManual) 328 defer subscriptionCleanup() 329 330 subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), "manual-subscription", subscriptionHasCondition(operatorsv1alpha1.SubscriptionInstallPlanPending, corev1.ConditionTrue, string(operatorsv1alpha1.InstallPlanPhaseRequiresApproval), "")) 331 require.NoError(GinkgoT(), err) 332 require.NotNil(GinkgoT(), subscription) 333 334 installPlan, err := fetchInstallPlanWithNamespace(GinkgoT(), crc, subscription.Status.Install.Name, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseRequiresApproval)) 335 require.NoError(GinkgoT(), err) 336 require.NotNil(GinkgoT(), installPlan) 337 338 require.Equal(GinkgoT(), operatorsv1alpha1.ApprovalManual, installPlan.Spec.Approval) 339 require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseRequiresApproval, installPlan.Status.Phase) 340 341 By(`Delete the current installplan`) 342 err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Delete(context.Background(), installPlan.Name, metav1.DeleteOptions{}) 343 require.NoError(GinkgoT(), err) 344 345 var ipName string 346 Eventually(func() bool { 347 fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), "manual-subscription", metav1.GetOptions{}) 348 if err != nil { 349 return false 350 } 351 if fetched.Status.Install != nil { 352 ipName = fetched.Status.Install.Name 353 return fetched.Status.Install.Name != installPlan.Name 354 } 355 return false 356 }, 5*time.Minute, 10*time.Second).Should(BeTrue()) 357 358 By(`Fetch new installplan`) 359 newInstallPlan, err := fetchInstallPlanWithNamespace(GinkgoT(), crc, ipName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseRequiresApproval)) 360 require.NoError(GinkgoT(), err) 361 require.NotNil(GinkgoT(), newInstallPlan) 362 363 require.NotEqual(GinkgoT(), installPlan.Name, newInstallPlan.Name, "expected new installplan recreated") 364 require.Equal(GinkgoT(), operatorsv1alpha1.ApprovalManual, newInstallPlan.Spec.Approval) 365 require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseRequiresApproval, newInstallPlan.Status.Phase) 366 367 By(`Set the InstallPlan's approved to True`) 368 Eventually(Apply(newInstallPlan, func(p *operatorsv1alpha1.InstallPlan) error { 369 370 p.Spec.Approved = true 371 return nil 372 })).Should(Succeed()) 373 374 subscription, err = fetchSubscription(crc, generatedNamespace.GetName(), "manual-subscription", subscriptionStateAtLatestChecker()) 375 require.NoError(GinkgoT(), err) 376 require.NotNil(GinkgoT(), subscription) 377 378 _, err = fetchCSV(crc, generatedNamespace.GetName(), subscription.Status.CurrentCSV, buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded)) 379 require.NoError(GinkgoT(), err) 380 }) 381 382 It("with starting CSV", func() { 383 384 crdPlural := genName("ins") 385 crdName := crdPlural + ".cluster.com" 386 387 crd := apiextensionsv1.CustomResourceDefinition{ 388 ObjectMeta: metav1.ObjectMeta{ 389 Name: crdName, 390 }, 391 Spec: apiextensionsv1.CustomResourceDefinitionSpec{ 392 Group: "cluster.com", 393 Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ 394 { 395 Name: "v1alpha1", 396 Served: true, 397 Storage: true, 398 Schema: &apiextensionsv1.CustomResourceValidation{ 399 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 400 Type: "object", 401 Description: "my crd schema", 402 }, 403 }, 404 }, 405 }, 406 Names: apiextensionsv1.CustomResourceDefinitionNames{ 407 Plural: crdPlural, 408 Singular: crdPlural, 409 Kind: crdPlural, 410 ListKind: "list" + crdPlural, 411 }, 412 Scope: apiextensionsv1.NamespaceScoped, 413 }, 414 } 415 416 By(`Create CSV`) 417 packageName := genName("nginx-") 418 stableChannel := "stable" 419 420 csvA := newCSV("nginx-a", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) 421 csvB := newCSV("nginx-b", generatedNamespace.GetName(), "nginx-a", semver.MustParse("0.2.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) 422 423 By(`Create PackageManifests`) 424 manifests := []registry.PackageManifest{ 425 { 426 PackageName: packageName, 427 Channels: []registry.PackageChannel{ 428 {Name: stableChannel, CurrentCSVName: csvB.GetName()}, 429 }, 430 DefaultChannelName: stableChannel, 431 }, 432 } 433 434 By(`Create the CatalogSource`) 435 catalogSourceName := genName("mock-nginx-") 436 _, cleanupCatalogSource := createInternalCatalogSource(c, crc, catalogSourceName, generatedNamespace.GetName(), manifests, []apiextensionsv1.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{csvA, csvB}) 437 defer cleanupCatalogSource() 438 439 By(`Attempt to get the catalog source before creating install plan`) 440 _, err := fetchCatalogSourceOnStatus(crc, catalogSourceName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 441 require.NoError(GinkgoT(), err) 442 443 subscriptionName := genName("sub-nginx-") 444 cleanupSubscription := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, catalogSourceName, packageName, stableChannel, csvA.GetName(), operatorsv1alpha1.ApprovalManual) 445 defer cleanupSubscription() 446 447 subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) 448 require.NoError(GinkgoT(), err) 449 require.NotNil(GinkgoT(), subscription) 450 451 installPlanName := subscription.Status.Install.Name 452 453 By(`Wait for InstallPlan to be status: Complete before checking resource presence`) 454 requiresApprovalChecker := buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseRequiresApproval) 455 fetchedInstallPlan, err := fetchInstallPlanWithNamespace(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), requiresApprovalChecker) 456 require.NoError(GinkgoT(), err) 457 458 By(`Ensure that only 1 installplan was created`) 459 ips, err := crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).List(context.Background(), metav1.ListOptions{}) 460 require.NoError(GinkgoT(), err) 461 require.Len(GinkgoT(), ips.Items, 1) 462 463 By(`Ensure that csvA and its crd are found in the plan`) 464 csvFound := false 465 crdFound := false 466 for _, s := range fetchedInstallPlan.Status.Plan { 467 require.Equal(GinkgoT(), csvA.GetName(), s.Resolving, "unexpected resolution found") 468 require.Equal(GinkgoT(), operatorsv1alpha1.StepStatusUnknown, s.Status, "status should be unknown") 469 require.Equal(GinkgoT(), catalogSourceName, s.Resource.CatalogSource, "incorrect catalogsource on step resource") 470 switch kind := s.Resource.Kind; kind { 471 case operatorsv1alpha1.ClusterServiceVersionKind: 472 require.Equal(GinkgoT(), csvA.GetName(), s.Resource.Name, "unexpected csv found") 473 csvFound = true 474 case "CustomResourceDefinition": 475 require.Equal(GinkgoT(), crdName, s.Resource.Name, "unexpected crd found") 476 crdFound = true 477 default: 478 GinkgoT().Fatalf("unexpected resource kind found in installplan: %s", kind) 479 } 480 } 481 require.True(GinkgoT(), csvFound, "expected csv not found in installplan") 482 require.True(GinkgoT(), crdFound, "expected crd not found in installplan") 483 484 By(`Ensure that csvB is not found in the plan`) 485 csvFound = false 486 for _, s := range fetchedInstallPlan.Status.Plan { 487 require.Equal(GinkgoT(), csvA.GetName(), s.Resolving, "unexpected resolution found") 488 require.Equal(GinkgoT(), operatorsv1alpha1.StepStatusUnknown, s.Status, "status should be unknown") 489 require.Equal(GinkgoT(), catalogSourceName, s.Resource.CatalogSource, "incorrect catalogsource on step resource") 490 switch kind := s.Resource.Kind; kind { 491 case operatorsv1alpha1.ClusterServiceVersionKind: 492 if s.Resource.Name == csvB.GetName() { 493 csvFound = true 494 } 495 } 496 } 497 require.False(GinkgoT(), csvFound, "expected csv not found in installplan") 498 499 By(`Approve the installplan and wait for csvA to be installed`) 500 fetchedInstallPlan.Spec.Approved = true 501 _, err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Update(context.Background(), fetchedInstallPlan, metav1.UpdateOptions{}) 502 require.NoError(GinkgoT(), err) 503 504 _, err = fetchCSV(crc, generatedNamespace.GetName(), csvA.GetName(), csvSucceededChecker) 505 require.NoError(GinkgoT(), err) 506 507 By(`Wait for the subscription to begin upgrading to csvB`) 508 By(`The upgrade changes the installplanref on the subscription`) 509 Eventually(func() (bool, error) { 510 subscription, err = crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), subscriptionName, metav1.GetOptions{}) 511 return subscription != nil && subscription.Status.InstallPlanRef.Name != fetchedInstallPlan.GetName() && subscription.Status.State == operatorsv1alpha1.SubscriptionStateUpgradePending, err 512 }, 5*time.Minute, 1*time.Second).Should(BeTrue(), "expected new installplan for upgraded csv") 513 514 upgradeInstallPlan, err := fetchInstallPlanWithNamespace(GinkgoT(), crc, subscription.Status.InstallPlanRef.Name, generatedNamespace.GetName(), requiresApprovalChecker) 515 require.NoError(GinkgoT(), err) 516 517 By(`Approve the upgrade installplan and wait for`) 518 upgradeInstallPlan.Spec.Approved = true 519 _, err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Update(context.Background(), upgradeInstallPlan, metav1.UpdateOptions{}) 520 require.NoError(GinkgoT(), err) 521 522 _, err = fetchCSV(crc, generatedNamespace.GetName(), csvB.GetName(), csvSucceededChecker) 523 require.NoError(GinkgoT(), err) 524 525 By(`Ensure that 2 installplans were created`) 526 ips, err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).List(context.Background(), metav1.ListOptions{}) 527 require.NoError(GinkgoT(), err) 528 require.Len(GinkgoT(), ips.Items, 2) 529 }) 530 531 It("[FLAKE] updates multiple intermediates", func() { 532 By(`issue: https://github.com/operator-framework/operator-lifecycle-manager/issues/2635`) 533 534 crd := newCRD("ins") 535 536 By(`Create CSV`) 537 packageName := genName("nginx-") 538 stableChannel := "stable" 539 540 csvA := newCSV("nginx-a", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) 541 csvB := newCSV("nginx-b", generatedNamespace.GetName(), "nginx-a", semver.MustParse("0.2.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) 542 csvC := newCSV("nginx-c", generatedNamespace.GetName(), "nginx-b", semver.MustParse("0.3.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) 543 544 By(`Create PackageManifests`) 545 manifests := []registry.PackageManifest{ 546 { 547 PackageName: packageName, 548 Channels: []registry.PackageChannel{ 549 {Name: stableChannel, CurrentCSVName: csvA.GetName()}, 550 }, 551 DefaultChannelName: stableChannel, 552 }, 553 } 554 555 By(`Create the CatalogSource with just one version`) 556 catalogSourceName := genName("mock-nginx-") 557 _, cleanupCatalogSource := createInternalCatalogSource(c, crc, catalogSourceName, generatedNamespace.GetName(), manifests, []apiextensionsv1.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{csvA}) 558 defer cleanupCatalogSource() 559 560 By(`Attempt to get the catalog source before creating install plan`) 561 _, err := fetchCatalogSourceOnStatus(crc, catalogSourceName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 562 require.NoError(GinkgoT(), err) 563 564 subscriptionName := genName("sub-nginx-") 565 cleanupSubscription := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, catalogSourceName, packageName, stableChannel, csvA.GetName(), operatorsv1alpha1.ApprovalAutomatic) 566 defer cleanupSubscription() 567 568 subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) 569 require.NoError(GinkgoT(), err) 570 require.NotNil(GinkgoT(), subscription) 571 572 By(`Wait for csvA to be installed`) 573 _, err = fetchCSV(crc, generatedNamespace.GetName(), csvA.GetName(), csvSucceededChecker) 574 require.NoError(GinkgoT(), err) 575 576 By(`Set up async watches that will fail the test if csvB doesn't get created in between csvA and csvC`) 577 var wg sync.WaitGroup 578 go func(t GinkgoTInterface) { 579 defer GinkgoRecover() 580 wg.Add(1) 581 defer wg.Done() 582 _, err := fetchCSV(crc, generatedNamespace.GetName(), csvB.GetName(), csvReplacingChecker) 583 require.NoError(GinkgoT(), err) 584 }(GinkgoT()) 585 By(`Update the catalog to include multiple updates`) 586 packages := []registry.PackageManifest{ 587 { 588 PackageName: packageName, 589 Channels: []registry.PackageChannel{ 590 {Name: stableChannel, CurrentCSVName: csvC.GetName()}, 591 }, 592 DefaultChannelName: stableChannel, 593 }, 594 } 595 596 updateInternalCatalog(GinkgoT(), c, crc, catalogSourceName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{csvA, csvB, csvC}, packages) 597 598 By(`wait for checks on intermediate csvs to succeed`) 599 wg.Wait() 600 601 By(`Wait for csvC to be installed`) 602 _, err = fetchCSV(crc, generatedNamespace.GetName(), csvC.GetName(), csvSucceededChecker) 603 require.NoError(GinkgoT(), err) 604 605 By(`Should eventually GC the CSVs`) 606 err = waitForCsvToDelete(generatedNamespace.GetName(), csvA.Name, crc) 607 Expect(err).ShouldNot(HaveOccurred()) 608 609 err = waitForCsvToDelete(generatedNamespace.GetName(), csvB.Name, crc) 610 Expect(err).ShouldNot(HaveOccurred()) 611 612 By(`TODO: check installplans, subscription status, etc`) 613 }) 614 615 It("updates existing install plan", func() { 616 By(`TestSubscriptionUpdatesExistingInstallPlan ensures that an existing InstallPlan has the appropriate approval requirement from Subscription.`) 617 618 Skip("ToDo: This test was skipped before ginkgo conversion") 619 620 By(`Create CSV`) 621 packageName := genName("nginx-") 622 stableChannel := "stable" 623 624 csvA := newCSV("nginx-a", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), nil, nil, nil) 625 csvB := newCSV("nginx-b", generatedNamespace.GetName(), "nginx-a", semver.MustParse("0.2.0"), nil, nil, nil) 626 627 By(`Create PackageManifests`) 628 manifests := []registry.PackageManifest{ 629 { 630 PackageName: packageName, 631 Channels: []registry.PackageChannel{ 632 {Name: stableChannel, CurrentCSVName: csvB.GetName()}, 633 }, 634 DefaultChannelName: stableChannel, 635 }, 636 } 637 638 By(`Create the CatalogSource with just one version`) 639 catalogSourceName := genName("mock-nginx-") 640 _, cleanupCatalogSource := createInternalCatalogSource(c, crc, catalogSourceName, generatedNamespace.GetName(), manifests, nil, []operatorsv1alpha1.ClusterServiceVersion{csvA, csvB}) 641 defer cleanupCatalogSource() 642 643 By(`Attempt to get the catalog source before creating install plan`) 644 _, err := fetchCatalogSourceOnStatus(crc, catalogSourceName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 645 require.NoError(GinkgoT(), err) 646 647 By(`Create a subscription to just get an InstallPlan for csvB`) 648 subscriptionName := genName("sub-nginx-") 649 createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, catalogSourceName, packageName, stableChannel, csvB.GetName(), operatorsv1alpha1.ApprovalAutomatic) 650 651 By(`Wait for csvB to be installed`) 652 _, err = fetchCSV(crc, generatedNamespace.GetName(), csvB.GetName(), csvSucceededChecker) 653 require.NoError(GinkgoT(), err) 654 655 subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) 656 fetchedInstallPlan, err := fetchInstallPlanWithNamespace(GinkgoT(), crc, subscription.Status.InstallPlanRef.Name, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete)) 657 658 By(`Delete this subscription`) 659 err = crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), *metav1.NewDeleteOptions(0), metav1.ListOptions{}) 660 require.NoError(GinkgoT(), err) 661 By(`Delete orphaned csvB`) 662 require.NoError(GinkgoT(), crc.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Delete(context.Background(), csvB.GetName(), metav1.DeleteOptions{})) 663 664 By(`Create an InstallPlan for csvB`) 665 ip := &operatorsv1alpha1.InstallPlan{ 666 ObjectMeta: metav1.ObjectMeta{ 667 GenerateName: "install-", 668 Namespace: generatedNamespace.GetName(), 669 }, 670 Spec: operatorsv1alpha1.InstallPlanSpec{ 671 ClusterServiceVersionNames: []string{csvB.GetName()}, 672 Approval: operatorsv1alpha1.ApprovalAutomatic, 673 Approved: false, 674 }, 675 } 676 ip2, err := crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Create(context.Background(), ip, metav1.CreateOptions{}) 677 require.NoError(GinkgoT(), err) 678 679 ip2.Status = operatorsv1alpha1.InstallPlanStatus{ 680 Plan: fetchedInstallPlan.Status.Plan, 681 CatalogSources: []string{catalogSourceName}, 682 } 683 684 _, err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).UpdateStatus(context.Background(), ip2, metav1.UpdateOptions{}) 685 require.NoError(GinkgoT(), err) 686 687 subscriptionName = genName("sub-nginx-") 688 cleanupSubscription := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, catalogSourceName, packageName, stableChannel, csvA.GetName(), operatorsv1alpha1.ApprovalManual) 689 defer cleanupSubscription() 690 691 subscription, err = fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) 692 require.NoError(GinkgoT(), err) 693 require.NotNil(GinkgoT(), subscription) 694 695 installPlanName := subscription.Status.Install.Name 696 697 By(`Wait for InstallPlan to be status: Complete before checking resource presence`) 698 requiresApprovalChecker := buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseRequiresApproval) 699 fetchedInstallPlan, err = fetchInstallPlanWithNamespace(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), requiresApprovalChecker) 700 require.NoError(GinkgoT(), err) 701 702 By(`Approve the installplan and wait for csvA to be installed`) 703 fetchedInstallPlan.Spec.Approved = true 704 _, err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Update(context.Background(), fetchedInstallPlan, metav1.UpdateOptions{}) 705 require.NoError(GinkgoT(), err) 706 707 By(`Wait for csvA to be installed`) 708 _, err = fetchCSV(crc, generatedNamespace.GetName(), csvA.GetName(), csvSucceededChecker) 709 require.NoError(GinkgoT(), err) 710 711 By(`Wait for the subscription to begin upgrading to csvB`) 712 subscription, err = fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionStateUpgradePendingChecker()) 713 require.NoError(GinkgoT(), err) 714 715 By(`Fetch existing csvB installPlan`) 716 fetchedInstallPlan, err = fetchInstallPlanWithNamespace(GinkgoT(), crc, subscription.Status.InstallPlanRef.Name, generatedNamespace.GetName(), requiresApprovalChecker) 717 require.NoError(GinkgoT(), err) 718 require.Equal(GinkgoT(), ip2.GetName(), subscription.Status.InstallPlanRef.Name, "expected new installplan is the same with pre-exising one") 719 720 By(`Approve the installplan and wait for csvB to be installed`) 721 fetchedInstallPlan.Spec.Approved = true 722 _, err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Update(context.Background(), fetchedInstallPlan, metav1.UpdateOptions{}) 723 require.NoError(GinkgoT(), err) 724 725 By(`Wait for csvB to be installed`) 726 _, err = fetchCSV(crc, generatedNamespace.GetName(), csvB.GetName(), csvSucceededChecker) 727 require.NoError(GinkgoT(), err) 728 }) 729 730 Describe("puppeting CatalogSource health status", func() { 731 var ( 732 getOpts metav1.GetOptions 733 deleteOpts *metav1.DeleteOptions 734 ) 735 736 BeforeEach(func() { 737 getOpts = metav1.GetOptions{} 738 deleteOpts = &metav1.DeleteOptions{} 739 }) 740 741 AfterEach(func() { 742 err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{}) 743 Expect(err).NotTo(HaveOccurred()) 744 }) 745 746 When("missing target catalog", func() { 747 It("should surface the missing catalog", func() { 748 By(`TestSubscriptionStatusMissingTargetCatalogSource ensures that a Subscription has the appropriate status condition when`) 749 By(`its target catalog is missing.`) 750 By(` BySteps:`) 751 By(`1. Generate an initial CatalogSource in the target namespace`) 752 By(`2. Generate Subscription, "sub", targetting non-existent CatalogSource, "missing"`) 753 By(`3. Wait for sub status to show SubscriptionCatalogSourcesUnhealthy with status True, reason CatalogSourcesUpdated, and appropriate missing message`) 754 By(`4. Update sub's spec to target the "mysubscription"`) 755 By(`5. Wait for sub's status to show SubscriptionCatalogSourcesUnhealthy with status False, reason AllCatalogSourcesHealthy, and reason "all available catalogsources are healthy"`) 756 By(`6. Wait for sub to succeed`) 757 err := initCatalog(GinkgoT(), generatedNamespace.GetName(), c, crc) 758 Expect(err).NotTo(HaveOccurred()) 759 760 missingName := "missing" 761 cleanup := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), testSubscriptionName, missingName, testPackageName, betaChannel, "", operatorsv1alpha1.ApprovalAutomatic) 762 defer cleanup() 763 764 By("detecting its absence") 765 sub, err := fetchSubscription(crc, generatedNamespace.GetName(), testSubscriptionName, subscriptionHasCondition(operatorsv1alpha1.SubscriptionCatalogSourcesUnhealthy, corev1.ConditionTrue, operatorsv1alpha1.UnhealthyCatalogSourceFound, fmt.Sprintf("targeted catalogsource %s/%s missing", generatedNamespace.GetName(), missingName))) 766 Expect(err).NotTo(HaveOccurred()) 767 Expect(sub).ToNot(BeNil()) 768 769 By("updating the subscription to target an existing catsrc") 770 Eventually(func() error { 771 sub, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), testSubscriptionName, metav1.GetOptions{}) 772 if err != nil { 773 return err 774 } 775 if sub == nil { 776 return fmt.Errorf("subscription is nil") 777 } 778 sub.Spec.CatalogSource = catalogSourceName 779 _, err = crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Update(context.Background(), sub, metav1.UpdateOptions{}) 780 return err 781 }).Should(Succeed()) 782 783 By(`Wait for SubscriptionCatalogSourcesUnhealthy to be false`) 784 By("detecting a new existing target") 785 _, err = fetchSubscription(crc, generatedNamespace.GetName(), testSubscriptionName, subscriptionHasCondition(operatorsv1alpha1.SubscriptionCatalogSourcesUnhealthy, corev1.ConditionFalse, operatorsv1alpha1.AllCatalogSourcesHealthy, "all available catalogsources are healthy")) 786 Expect(err).NotTo(HaveOccurred()) 787 788 By(`Wait for success`) 789 _, err = fetchSubscription(crc, generatedNamespace.GetName(), testSubscriptionName, subscriptionStateAtLatestChecker()) 790 Expect(err).NotTo(HaveOccurred()) 791 }) 792 }) 793 794 When("the target catalog's sourceType", func() { 795 Context("is unknown", func() { 796 It("should surface catalog health", func() { 797 cs := &operatorsv1alpha1.CatalogSource{ 798 TypeMeta: metav1.TypeMeta{ 799 Kind: operatorsv1alpha1.CatalogSourceKind, 800 APIVersion: operatorsv1alpha1.CatalogSourceCRDAPIVersion, 801 }, 802 ObjectMeta: metav1.ObjectMeta{ 803 Name: "cs", 804 }, 805 Spec: operatorsv1alpha1.CatalogSourceSpec{ 806 SourceType: "goose", 807 GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ 808 SecurityContextConfig: operatorsv1alpha1.Restricted, 809 }, 810 }, 811 } 812 813 var err error 814 cs, err = crc.OperatorsV1alpha1().CatalogSources(generatedNamespace.GetName()).Create(context.Background(), cs, metav1.CreateOptions{}) 815 defer func() { 816 err = crc.OperatorsV1alpha1().CatalogSources(cs.GetNamespace()).Delete(context.Background(), cs.GetName(), *deleteOpts) 817 Expect(err).ToNot(HaveOccurred()) 818 }() 819 820 subName := genName("sub-") 821 cleanup := createSubscriptionForCatalog( 822 crc, 823 cs.GetNamespace(), 824 subName, 825 cs.GetName(), 826 testPackageName, 827 betaChannel, 828 "", 829 operatorsv1alpha1.ApprovalManual, 830 ) 831 defer cleanup() 832 833 var sub *operatorsv1alpha1.Subscription 834 sub, err = fetchSubscription( 835 crc, 836 cs.GetNamespace(), 837 subName, 838 subscriptionHasCondition( 839 operatorsv1alpha1.SubscriptionCatalogSourcesUnhealthy, 840 corev1.ConditionTrue, 841 operatorsv1alpha1.UnhealthyCatalogSourceFound, 842 fmt.Sprintf("targeted catalogsource %s/%s unhealthy", cs.GetNamespace(), cs.GetName()), 843 ), 844 ) 845 Expect(err).NotTo(HaveOccurred()) 846 Expect(sub).ToNot(BeNil()) 847 848 By(`Get the latest CatalogSource`) 849 cs, err = crc.OperatorsV1alpha1().CatalogSources(cs.GetNamespace()).Get(context.Background(), cs.GetName(), getOpts) 850 Expect(err).NotTo(HaveOccurred()) 851 Expect(cs).ToNot(BeNil()) 852 }) 853 }) 854 855 Context("is grpc and its spec is missing the address and image fields", func() { 856 It("should surface catalog health", func() { 857 By(`Create a CatalogSource pointing to the grpc pod`) 858 cs := &operatorsv1alpha1.CatalogSource{ 859 TypeMeta: metav1.TypeMeta{ 860 Kind: operatorsv1alpha1.CatalogSourceKind, 861 APIVersion: operatorsv1alpha1.CatalogSourceCRDAPIVersion, 862 }, 863 ObjectMeta: metav1.ObjectMeta{ 864 Name: genName("cs-"), 865 Namespace: generatedNamespace.GetName(), 866 }, 867 Spec: operatorsv1alpha1.CatalogSourceSpec{ 868 SourceType: operatorsv1alpha1.SourceTypeGrpc, 869 GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ 870 SecurityContextConfig: operatorsv1alpha1.Restricted, 871 }, 872 }, 873 } 874 875 var err error 876 cs, err = crc.OperatorsV1alpha1().CatalogSources(generatedNamespace.GetName()).Create(context.Background(), cs, metav1.CreateOptions{}) 877 defer func() { 878 err = crc.OperatorsV1alpha1().CatalogSources(cs.GetNamespace()).Delete(context.Background(), cs.GetName(), *deleteOpts) 879 Expect(err).ToNot(HaveOccurred()) 880 }() 881 882 By(`Wait for the CatalogSource status to be updated to reflect its invalid spec`) 883 _, err = fetchCatalogSourceOnStatus(crc, cs.GetName(), cs.GetNamespace(), catalogSourceInvalidSpec) 884 Expect(err).ToNot(HaveOccurred(), "catalog source did not become ready") 885 886 subName := genName("sub-") 887 cleanup := createSubscriptionForCatalog( 888 crc, 889 cs.GetNamespace(), 890 subName, 891 cs.GetName(), 892 testPackageName, 893 betaChannel, 894 "", 895 operatorsv1alpha1.ApprovalManual, 896 ) 897 defer cleanup() 898 899 var sub *operatorsv1alpha1.Subscription 900 sub, err = fetchSubscription( 901 crc, 902 cs.GetNamespace(), 903 subName, 904 subscriptionHasCondition( 905 operatorsv1alpha1.SubscriptionCatalogSourcesUnhealthy, 906 corev1.ConditionTrue, 907 operatorsv1alpha1.UnhealthyCatalogSourceFound, 908 fmt.Sprintf("targeted catalogsource %s/%s unhealthy", cs.GetNamespace(), cs.GetName()), 909 ), 910 ) 911 Expect(err).NotTo(HaveOccurred()) 912 Expect(sub).ToNot(BeNil()) 913 }) 914 }) 915 916 Context("is internal and its spec is missing the configmap reference", func() { 917 It("should surface catalog health", func() { 918 cs := &operatorsv1alpha1.CatalogSource{ 919 TypeMeta: metav1.TypeMeta{ 920 Kind: operatorsv1alpha1.CatalogSourceKind, 921 APIVersion: operatorsv1alpha1.CatalogSourceCRDAPIVersion, 922 }, 923 ObjectMeta: metav1.ObjectMeta{ 924 Name: genName("cs-"), 925 Namespace: generatedNamespace.GetName(), 926 }, 927 Spec: operatorsv1alpha1.CatalogSourceSpec{ 928 SourceType: operatorsv1alpha1.SourceTypeInternal, 929 GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ 930 SecurityContextConfig: operatorsv1alpha1.Restricted, 931 }, 932 }, 933 } 934 935 var err error 936 cs, err = crc.OperatorsV1alpha1().CatalogSources(generatedNamespace.GetName()).Create(context.Background(), cs, metav1.CreateOptions{}) 937 defer func() { 938 err = crc.OperatorsV1alpha1().CatalogSources(cs.GetNamespace()).Delete(context.Background(), cs.GetName(), *deleteOpts) 939 Expect(err).ToNot(HaveOccurred()) 940 }() 941 942 subName := genName("sub-") 943 cleanup := createSubscriptionForCatalog( 944 crc, 945 cs.GetNamespace(), 946 subName, 947 cs.GetName(), 948 testPackageName, 949 betaChannel, 950 "", 951 operatorsv1alpha1.ApprovalManual, 952 ) 953 defer cleanup() 954 955 var sub *operatorsv1alpha1.Subscription 956 sub, err = fetchSubscription( 957 crc, 958 cs.GetNamespace(), 959 subName, 960 subscriptionHasCondition( 961 operatorsv1alpha1.SubscriptionCatalogSourcesUnhealthy, 962 corev1.ConditionTrue, 963 operatorsv1alpha1.UnhealthyCatalogSourceFound, 964 fmt.Sprintf("targeted catalogsource %s/%s unhealthy", cs.GetNamespace(), cs.GetName()), 965 ), 966 ) 967 Expect(err).NotTo(HaveOccurred()) 968 Expect(sub).ToNot(BeNil()) 969 }) 970 }) 971 972 Context("is configmap and its spec is missing the configmap reference", func() { 973 It("should surface catalog health", func() { 974 cs := &operatorsv1alpha1.CatalogSource{ 975 TypeMeta: metav1.TypeMeta{ 976 Kind: operatorsv1alpha1.CatalogSourceKind, 977 APIVersion: operatorsv1alpha1.CatalogSourceCRDAPIVersion, 978 }, 979 ObjectMeta: metav1.ObjectMeta{ 980 Name: genName("cs-"), 981 Namespace: generatedNamespace.GetName(), 982 }, 983 Spec: operatorsv1alpha1.CatalogSourceSpec{ 984 SourceType: operatorsv1alpha1.SourceTypeInternal, 985 GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ 986 SecurityContextConfig: operatorsv1alpha1.Restricted, 987 }, 988 }, 989 } 990 991 var err error 992 cs, err = crc.OperatorsV1alpha1().CatalogSources(generatedNamespace.GetName()).Create(context.Background(), cs, metav1.CreateOptions{}) 993 defer func() { 994 err = crc.OperatorsV1alpha1().CatalogSources(cs.GetNamespace()).Delete(context.Background(), cs.GetName(), *deleteOpts) 995 Expect(err).ToNot(HaveOccurred()) 996 }() 997 998 subName := genName("sub-") 999 cleanup := createSubscriptionForCatalog( 1000 crc, 1001 cs.GetNamespace(), 1002 subName, 1003 cs.GetName(), 1004 testPackageName, 1005 betaChannel, 1006 "", 1007 operatorsv1alpha1.ApprovalAutomatic, 1008 ) 1009 defer cleanup() 1010 1011 var sub *operatorsv1alpha1.Subscription 1012 sub, err = fetchSubscription( 1013 crc, 1014 cs.GetNamespace(), 1015 subName, 1016 subscriptionHasCondition( 1017 operatorsv1alpha1.SubscriptionCatalogSourcesUnhealthy, 1018 corev1.ConditionTrue, 1019 operatorsv1alpha1.UnhealthyCatalogSourceFound, 1020 fmt.Sprintf("targeted catalogsource %s/%s unhealthy", cs.GetNamespace(), cs.GetName()), 1021 ), 1022 ) 1023 Expect(err).NotTo(HaveOccurred()) 1024 Expect(sub).ToNot(BeNil()) 1025 }) 1026 }) 1027 }) 1028 1029 }) 1030 1031 It("can reconcile InstallPlan status", func() { 1032 By(`TestSubscriptionInstallPlanStatus ensures that a Subscription has the appropriate status conditions for possible referenced InstallPlan states.`) 1033 c := newKubeClient() 1034 crc := newCRClient() 1035 1036 By(`Create CatalogSource, cs, in ns`) 1037 pkgName := genName("pkg-") 1038 channelName := genName("channel-") 1039 crd := newCRD(pkgName) 1040 csv := newCSV(pkgName, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) 1041 manifests := []registry.PackageManifest{ 1042 { 1043 PackageName: pkgName, 1044 Channels: []registry.PackageChannel{ 1045 {Name: channelName, CurrentCSVName: csv.GetName()}, 1046 }, 1047 DefaultChannelName: channelName, 1048 }, 1049 } 1050 catalogName := genName("catalog-") 1051 _, cleanupCatalogSource := createInternalCatalogSource(c, crc, catalogName, generatedNamespace.GetName(), manifests, []apiextensionsv1.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{csv}) 1052 defer cleanupCatalogSource() 1053 _, err := fetchCatalogSourceOnStatus(crc, catalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 1054 Expect(err).ToNot(HaveOccurred()) 1055 1056 By(`Create Subscription to a package of cs in ns, sub`) 1057 subName := genName("sub-") 1058 defer createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subName, catalogName, pkgName, channelName, pkgName, operatorsv1alpha1.ApprovalAutomatic)() 1059 1060 By(`Wait for the package from sub to install successfully with no remaining InstallPlan status conditions`) 1061 checker := subscriptionStateAtLatestChecker() 1062 sub, err := fetchSubscription(crc, generatedNamespace.GetName(), subName, func(s *operatorsv1alpha1.Subscription) bool { 1063 for _, cond := range s.Status.Conditions { 1064 switch cond.Type { 1065 case operatorsv1alpha1.SubscriptionInstallPlanMissing, operatorsv1alpha1.SubscriptionInstallPlanPending, operatorsv1alpha1.SubscriptionInstallPlanFailed: 1066 return false 1067 } 1068 } 1069 return checker(s) 1070 }) 1071 Expect(err).ToNot(HaveOccurred()) 1072 Expect(sub).ToNot(BeNil()) 1073 1074 By(`Store conditions for later comparision`) 1075 conds := sub.Status.Conditions 1076 1077 ref := sub.Status.InstallPlanRef 1078 Expect(ref).ToNot(BeNil()) 1079 1080 By(`Get the InstallPlan`) 1081 plan := &operatorsv1alpha1.InstallPlan{} 1082 plan.SetNamespace(ref.Namespace) 1083 plan.SetName(ref.Name) 1084 1085 By(`Set the InstallPlan's approval mode to Manual`) 1086 Eventually(Apply(plan, func(p *operatorsv1alpha1.InstallPlan) error { 1087 p.Spec.Approval = operatorsv1alpha1.ApprovalManual 1088 1089 p.Spec.Approved = false 1090 return nil 1091 })).Should(Succeed()) 1092 1093 By(`Set the InstallPlan's phase to None`) 1094 Eventually(Apply(plan, func(p *operatorsv1alpha1.InstallPlan) error { 1095 p.Status.Phase = operatorsv1alpha1.InstallPlanPhaseNone 1096 return nil 1097 })).Should(Succeed()) 1098 1099 By(`Wait for sub to have status condition SubscriptionInstallPlanPending true and reason InstallPlanNotYetReconciled`) 1100 sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, func(s *operatorsv1alpha1.Subscription) bool { 1101 cond := s.Status.GetCondition(operatorsv1alpha1.SubscriptionInstallPlanPending) 1102 return cond.Status == corev1.ConditionTrue && cond.Reason == operatorsv1alpha1.InstallPlanNotYetReconciled 1103 }) 1104 Expect(err).ToNot(HaveOccurred()) 1105 1106 By(`Set the phase to InstallPlanPhaseRequiresApproval`) 1107 Eventually(Apply(plan, func(p *operatorsv1alpha1.InstallPlan) error { 1108 p.Status.Phase = operatorsv1alpha1.InstallPlanPhaseRequiresApproval 1109 return nil 1110 })).Should(Succeed()) 1111 1112 By(`Wait for sub to have status condition SubscriptionInstallPlanPending true and reason RequiresApproval`) 1113 sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, func(s *operatorsv1alpha1.Subscription) bool { 1114 cond := s.Status.GetCondition(operatorsv1alpha1.SubscriptionInstallPlanPending) 1115 return cond.Status == corev1.ConditionTrue && cond.Reason == string(operatorsv1alpha1.InstallPlanPhaseRequiresApproval) 1116 }) 1117 Expect(err).ToNot(HaveOccurred()) 1118 1119 By(`Set the phase to InstallPlanPhaseInstalling`) 1120 Eventually(Apply(plan, func(p *operatorsv1alpha1.InstallPlan) error { 1121 p.Status.Phase = operatorsv1alpha1.InstallPlanPhaseInstalling 1122 return nil 1123 })).Should(Succeed()) 1124 1125 By(`Wait for sub to have status condition SubscriptionInstallPlanPending true and reason Installing`) 1126 sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, func(s *operatorsv1alpha1.Subscription) bool { 1127 cond := s.Status.GetCondition(operatorsv1alpha1.SubscriptionInstallPlanPending) 1128 isConditionPresent := cond.Status == corev1.ConditionTrue && cond.Reason == string(operatorsv1alpha1.InstallPlanPhaseInstalling) 1129 1130 if isConditionPresent { 1131 return true 1132 } 1133 1134 // Sometimes the transition from installing to complete can be so quick that the test does not capture 1135 // the condition in the subscription before it is removed. To mitigate this, we check if the installplan 1136 // has transitioned to complete and exit out the fetch subscription loop if so. 1137 // This is a mitigation. We should probably fix this test appropriately. 1138 // issue: https://github.com/operator-framework/operator-lifecycle-manager/issues/2667 1139 ip, err := crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Get(context.TODO(), plan.Name, metav1.GetOptions{}) 1140 if err != nil { 1141 // retry on failure 1142 return false 1143 } 1144 isInstallPlanComplete := ip.Status.Phase == operatorsv1alpha1.InstallPlanPhaseComplete 1145 1146 return isInstallPlanComplete 1147 }) 1148 Expect(err).ToNot(HaveOccurred()) 1149 1150 By(`Set the phase to InstallPlanPhaseFailed and remove all status conditions`) 1151 Eventually(Apply(plan, func(p *operatorsv1alpha1.InstallPlan) error { 1152 p.Status.Phase = operatorsv1alpha1.InstallPlanPhaseFailed 1153 p.Status.Conditions = nil 1154 return nil 1155 })).Should(Succeed()) 1156 1157 By(`Wait for sub to have status condition SubscriptionInstallPlanFailed true and reason InstallPlanFailed`) 1158 sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, func(s *operatorsv1alpha1.Subscription) bool { 1159 cond := s.Status.GetCondition(operatorsv1alpha1.SubscriptionInstallPlanFailed) 1160 return cond.Status == corev1.ConditionTrue && cond.Reason == operatorsv1alpha1.InstallPlanFailed 1161 }) 1162 Expect(err).ToNot(HaveOccurred()) 1163 1164 By(`Set status condition of type Installed to false with reason InstallComponentFailed`) 1165 Eventually(Apply(plan, func(p *operatorsv1alpha1.InstallPlan) error { 1166 p.Status.Phase = operatorsv1alpha1.InstallPlanPhaseFailed 1167 failedCond := p.Status.GetCondition(operatorsv1alpha1.InstallPlanInstalled) 1168 failedCond.Status = corev1.ConditionFalse 1169 failedCond.Reason = operatorsv1alpha1.InstallPlanReasonComponentFailed 1170 p.Status.SetCondition(failedCond) 1171 return nil 1172 })).Should(Succeed()) 1173 1174 By(`Wait for sub to have status condition SubscriptionInstallPlanFailed true and reason InstallComponentFailed`) 1175 sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, func(s *operatorsv1alpha1.Subscription) bool { 1176 cond := s.Status.GetCondition(operatorsv1alpha1.SubscriptionInstallPlanFailed) 1177 return cond.Status == corev1.ConditionTrue && cond.Reason == string(operatorsv1alpha1.InstallPlanReasonComponentFailed) 1178 }) 1179 Expect(err).ToNot(HaveOccurred()) 1180 1181 By(`Delete the referenced InstallPlan`) 1182 Eventually(func() error { 1183 return crc.OperatorsV1alpha1().InstallPlans(ref.Namespace).Delete(context.Background(), ref.Name, metav1.DeleteOptions{}) 1184 }).Should(Succeed()) 1185 1186 By(`Wait for sub to have status condition SubscriptionInstallPlanMissing true`) 1187 sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, func(s *operatorsv1alpha1.Subscription) bool { 1188 return s.Status.GetCondition(operatorsv1alpha1.SubscriptionInstallPlanMissing).Status == corev1.ConditionTrue 1189 }) 1190 Expect(err).ToNot(HaveOccurred()) 1191 Expect(sub).ToNot(BeNil()) 1192 1193 By(`Ensure InstallPlan-related status conditions match what we're expecting`) 1194 for _, cond := range conds { 1195 switch condType := cond.Type; condType { 1196 case operatorsv1alpha1.SubscriptionInstallPlanPending, operatorsv1alpha1.SubscriptionInstallPlanFailed: 1197 require.FailNowf(GinkgoT(), "failed", "subscription contains unexpected installplan condition: %v", cond) 1198 case operatorsv1alpha1.SubscriptionInstallPlanMissing: 1199 require.Equal(GinkgoT(), operatorsv1alpha1.ReferencedInstallPlanNotFound, cond.Reason) 1200 } 1201 } 1202 }) 1203 1204 It("creation with pod config", func() { 1205 1206 newConfigClient := func(t GinkgoTInterface) configv1client.ConfigV1Interface { 1207 client, err := configv1client.NewForConfig(ctx.Ctx().RESTConfig()) 1208 require.NoError(GinkgoT(), err) 1209 1210 return client 1211 } 1212 1213 proxyEnvVarFunc := func(t GinkgoTInterface, client configv1client.ConfigV1Interface) []corev1.EnvVar { 1214 if discovery.ServerSupportsVersion(ctx.Ctx().KubeClient().KubernetesInterface().Discovery(), configv1.GroupVersion) != nil { 1215 return nil 1216 } 1217 1218 proxy, getErr := client.Proxies().Get(context.Background(), "cluster", metav1.GetOptions{}) 1219 if apierrors.IsNotFound(getErr) { 1220 return nil 1221 } 1222 require.NoError(GinkgoT(), getErr) 1223 require.NotNil(GinkgoT(), proxy) 1224 1225 proxyEnv := []corev1.EnvVar{} 1226 1227 if proxy.Status.HTTPProxy != "" { 1228 proxyEnv = append(proxyEnv, corev1.EnvVar{ 1229 Name: "HTTP_PROXY", 1230 Value: proxy.Status.HTTPProxy, 1231 }) 1232 } 1233 1234 if proxy.Status.HTTPSProxy != "" { 1235 proxyEnv = append(proxyEnv, corev1.EnvVar{ 1236 Name: "HTTPS_PROXY", 1237 Value: proxy.Status.HTTPSProxy, 1238 }) 1239 } 1240 1241 if proxy.Status.NoProxy != "" { 1242 proxyEnv = append(proxyEnv, corev1.EnvVar{ 1243 Name: "NO_PROXY", 1244 Value: proxy.Status.NoProxy, 1245 }) 1246 } 1247 1248 return proxyEnv 1249 } 1250 1251 kubeClient := newKubeClient() 1252 crClient := newCRClient() 1253 config := newConfigClient(GinkgoT()) 1254 1255 By(`Create a ConfigMap that is mounted to the operator via the subscription`) 1256 testConfigMapName := genName("test-configmap-") 1257 testConfigMap := &corev1.ConfigMap{ 1258 ObjectMeta: metav1.ObjectMeta{ 1259 Name: testConfigMapName, 1260 }, 1261 } 1262 1263 _, err := kubeClient.KubernetesInterface().CoreV1().ConfigMaps(generatedNamespace.GetName()).Create(context.Background(), testConfigMap, metav1.CreateOptions{}) 1264 require.NoError(GinkgoT(), err) 1265 defer func() { 1266 err := kubeClient.KubernetesInterface().CoreV1().ConfigMaps(generatedNamespace.GetName()).Delete(context.Background(), testConfigMap.Name, metav1.DeleteOptions{}) 1267 require.NoError(GinkgoT(), err) 1268 }() 1269 1270 By(`Configure the Subscription.`) 1271 1272 podEnv := []corev1.EnvVar{ 1273 { 1274 Name: "MY_ENV_VARIABLE1", 1275 Value: "value1", 1276 }, 1277 { 1278 Name: "MY_ENV_VARIABLE2", 1279 Value: "value2", 1280 }, 1281 } 1282 testVolumeName := genName("test-volume-") 1283 podVolumes := []corev1.Volume{ 1284 { 1285 Name: testVolumeName, 1286 VolumeSource: corev1.VolumeSource{ 1287 ConfigMap: &corev1.ConfigMapVolumeSource{ 1288 LocalObjectReference: corev1.LocalObjectReference{ 1289 Name: testConfigMapName, 1290 }, 1291 }, 1292 }, 1293 }, 1294 } 1295 1296 podVolumeMounts := []corev1.VolumeMount{ 1297 {Name: testVolumeName, MountPath: "/test"}, 1298 } 1299 1300 podTolerations := []corev1.Toleration{ 1301 { 1302 Key: "my-toleration-key", 1303 Value: "my-toleration-value", 1304 Effect: corev1.TaintEffectNoSchedule, 1305 Operator: corev1.TolerationOpEqual, 1306 }, 1307 } 1308 podResources := &corev1.ResourceRequirements{ 1309 Limits: corev1.ResourceList{ 1310 corev1.ResourceCPU: resource.MustParse("100m"), 1311 }, 1312 Requests: corev1.ResourceList{ 1313 corev1.ResourceCPU: resource.MustParse("100m"), 1314 corev1.ResourceMemory: resource.MustParse("128Mi"), 1315 }, 1316 } 1317 1318 podConfig := &operatorsv1alpha1.SubscriptionConfig{ 1319 Env: podEnv, 1320 Volumes: podVolumes, 1321 VolumeMounts: podVolumeMounts, 1322 Tolerations: podTolerations, 1323 Resources: podResources, 1324 } 1325 1326 permissions := deploymentPermissions() 1327 catsrc, subSpec, catsrcCleanup := newCatalogSource(GinkgoT(), kubeClient, crClient, "podconfig", generatedNamespace.GetName(), permissions) 1328 defer catsrcCleanup() 1329 1330 By(`Ensure that the catalog source is resolved before we create a subscription.`) 1331 _, err = fetchCatalogSourceOnStatus(crClient, catsrc.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 1332 require.NoError(GinkgoT(), err) 1333 1334 subscriptionName := genName("podconfig-sub-") 1335 subSpec.Config = podConfig 1336 cleanupSubscription := createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subSpec) 1337 defer cleanupSubscription() 1338 1339 subscription, err := fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionStateAtLatestChecker()) 1340 require.NoError(GinkgoT(), err) 1341 require.NotNil(GinkgoT(), subscription) 1342 1343 proxyEnv := proxyEnvVarFunc(GinkgoT(), config) 1344 expected := podEnv 1345 expected = append(expected, proxyEnv...) 1346 1347 Eventually(func() error { 1348 csv, err := fetchCSV(crClient, generatedNamespace.GetName(), subscription.Status.CurrentCSV, buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded)) 1349 if err != nil { 1350 return err 1351 } 1352 1353 return checkDeploymentWithPodConfiguration(kubeClient, csv, podConfig.Env, podConfig.Volumes, podConfig.VolumeMounts, podConfig.Tolerations, podConfig.Resources) 1354 }).Should(Succeed()) 1355 }) 1356 1357 It("creation with nodeSelector config", func() { 1358 kubeClient := newKubeClient() 1359 crClient := newCRClient() 1360 1361 By(`Create a ConfigMap that is mounted to the operator via the subscription`) 1362 testConfigMapName := genName("test-configmap-") 1363 testConfigMap := &corev1.ConfigMap{ 1364 ObjectMeta: metav1.ObjectMeta{ 1365 Name: testConfigMapName, 1366 }, 1367 } 1368 1369 _, err := kubeClient.KubernetesInterface().CoreV1().ConfigMaps(generatedNamespace.GetName()).Create(context.Background(), testConfigMap, metav1.CreateOptions{}) 1370 require.NoError(GinkgoT(), err) 1371 defer func() { 1372 err := kubeClient.KubernetesInterface().CoreV1().ConfigMaps(generatedNamespace.GetName()).Delete(context.Background(), testConfigMap.Name, metav1.DeleteOptions{}) 1373 require.NoError(GinkgoT(), err) 1374 }() 1375 1376 By(`Configure the Subscription.`) 1377 podNodeSelector := map[string]string{ 1378 "foo": "bar", 1379 } 1380 1381 podConfig := &operatorsv1alpha1.SubscriptionConfig{ 1382 NodeSelector: podNodeSelector, 1383 } 1384 1385 permissions := deploymentPermissions() 1386 catsrc, subSpec, catsrcCleanup := newCatalogSource(GinkgoT(), kubeClient, crClient, "podconfig", generatedNamespace.GetName(), permissions) 1387 defer catsrcCleanup() 1388 1389 By(`Ensure that the catalog source is resolved before we create a subscription.`) 1390 _, err = fetchCatalogSourceOnStatus(crClient, catsrc.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 1391 require.NoError(GinkgoT(), err) 1392 1393 subscriptionName := genName("podconfig-sub-") 1394 subSpec.Config = podConfig 1395 cleanupSubscription := createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subSpec) 1396 defer cleanupSubscription() 1397 1398 subscription, err := fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionStateAtLatestChecker()) 1399 require.NoError(GinkgoT(), err) 1400 require.NotNil(GinkgoT(), subscription) 1401 1402 csv, err := fetchCSV(crClient, generatedNamespace.GetName(), subscription.Status.CurrentCSV, buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseInstalling, operatorsv1alpha1.CSVPhaseSucceeded)) 1403 require.NoError(GinkgoT(), err) 1404 1405 Eventually(func() error { 1406 return checkDeploymentHasPodConfigNodeSelector(GinkgoT(), kubeClient, csv, podNodeSelector) 1407 }, timeout, interval).Should(Succeed()) 1408 1409 }) 1410 1411 It("[FLAKE] creation with dependencies", func() { 1412 1413 kubeClient := newKubeClient() 1414 crClient := newCRClient() 1415 1416 permissions := deploymentPermissions() 1417 1418 catsrc, subSpec, catsrcCleanup := newCatalogSourceWithDependencies(GinkgoT(), kubeClient, crClient, "podconfig", generatedNamespace.GetName(), permissions) 1419 defer catsrcCleanup() 1420 1421 By(`Ensure that the catalog source is resolved before we create a subscription.`) 1422 _, err := fetchCatalogSourceOnStatus(crClient, catsrc.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 1423 require.NoError(GinkgoT(), err) 1424 1425 By(`Create duplicates of the CatalogSource`) 1426 for i := 0; i < 10; i++ { 1427 duplicateCatsrc, _, duplicateCatSrcCleanup := newCatalogSourceWithDependencies(GinkgoT(), kubeClient, crClient, "podconfig", generatedNamespace.GetName(), permissions) 1428 defer duplicateCatSrcCleanup() 1429 1430 By(`Ensure that the catalog source is resolved before we create a subscription.`) 1431 _, err = fetchCatalogSourceOnStatus(crClient, duplicateCatsrc.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 1432 require.NoError(GinkgoT(), err) 1433 } 1434 1435 By(`Create a subscription that has a dependency`) 1436 subscriptionName := genName("podconfig-sub-") 1437 cleanupSubscription := createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subSpec) 1438 defer cleanupSubscription() 1439 1440 subscription, err := fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionStateAtLatestChecker()) 1441 require.NoError(GinkgoT(), err) 1442 require.NotNil(GinkgoT(), subscription) 1443 1444 By(`Check that a single catalog source was used to resolve the InstallPlan`) 1445 installPlan, err := fetchInstallPlanWithNamespace(GinkgoT(), crClient, subscription.Status.InstallPlanRef.Name, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete)) 1446 require.NoError(GinkgoT(), err) 1447 require.Len(GinkgoT(), installPlan.Status.CatalogSources, 1) 1448 }) 1449 1450 It("creation with dependencies required and provided in different versions of an operator in the same package", func() { 1451 By(` PARITY: this test covers the same scenario as the TestSolveOperators_PackageCannotSelfSatisfy unit test`) 1452 kubeClient := ctx.Ctx().KubeClient() 1453 crClient := ctx.Ctx().OperatorClient() 1454 1455 crd := newCRD(genName("ins")) 1456 crd2 := newCRD(genName("ins")) 1457 1458 By(`csvs for catalogsource 1`) 1459 csvs1 := make([]operatorsv1alpha1.ClusterServiceVersion, 0) 1460 1461 By(`csvs for catalogsource 2`) 1462 csvs2 := make([]operatorsv1alpha1.ClusterServiceVersion, 0) 1463 1464 testPackage := registry.PackageManifest{PackageName: "test-package"} 1465 By("Package A", func() { 1466 Step(1, "Default Channel: Stable", func() { 1467 testPackage.DefaultChannelName = stableChannel 1468 }) 1469 1470 Step(1, "Channel Stable", func() { 1471 Step(2, "Operator A (Requires CRD, CRD 2)", func() { 1472 csvA := newCSV("csv-a", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), nil, []apiextensionsv1.CustomResourceDefinition{crd, crd2}, nil) 1473 testPackage. 1474 Channels = append(testPackage. 1475 Channels, registry.PackageChannel{Name: stableChannel, CurrentCSVName: csvA.GetName()}) 1476 csvs1 = append(csvs1, csvA) 1477 }) 1478 }) 1479 1480 Step(1, "Channel Alpha", func() { 1481 Step(2, "Operator ABC (Provides: CRD, CRD 2)", func() { 1482 csvABC := newCSV("csv-abc", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd, crd2}, nil, nil) 1483 testPackage. 1484 Channels = append(testPackage. 1485 Channels, registry.PackageChannel{Name: alphaChannel, CurrentCSVName: csvABC.GetName()}) 1486 csvs1 = append(csvs1, csvABC) 1487 }) 1488 }) 1489 }) 1490 1491 anotherPackage := registry.PackageManifest{PackageName: "another-package"} 1492 By("Package B", func() { 1493 Step(1, "Default Channel: Stable", func() { 1494 anotherPackage.DefaultChannelName = stableChannel 1495 }) 1496 1497 Step(1, "Channel Stable", func() { 1498 Step(2, "Operator B (Provides: CRD)", func() { 1499 csvB := newCSV("csv-b", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) 1500 anotherPackage.Channels = append(anotherPackage.Channels, registry.PackageChannel{Name: stableChannel, CurrentCSVName: csvB.GetName()}) 1501 csvs1 = append(csvs1, csvB) 1502 }) 1503 }) 1504 1505 Step(1, "Channel Alpha", func() { 1506 Step(2, "Operator D (Provides: CRD)", func() { 1507 csvD := newCSV("csv-d", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) 1508 anotherPackage.Channels = append(anotherPackage.Channels, registry.PackageChannel{Name: alphaChannel, CurrentCSVName: csvD.GetName()}) 1509 csvs1 = append(csvs1, csvD) 1510 }) 1511 }) 1512 }) 1513 1514 packageBInCatsrc2 := registry.PackageManifest{PackageName: "another-package"} 1515 By("Package B", func() { 1516 Step(1, "Default Channel: Stable", func() { 1517 packageBInCatsrc2.DefaultChannelName = stableChannel 1518 }) 1519 1520 Step(1, "Channel Stable", func() { 1521 Step(2, "Operator C (Provides: CRD 2)", func() { 1522 csvC := newCSV("csv-c", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd2}, nil, nil) 1523 packageBInCatsrc2.Channels = append(packageBInCatsrc2.Channels, registry.PackageChannel{Name: stableChannel, CurrentCSVName: csvC.GetName()}) 1524 csvs2 = append(csvs2, csvC) 1525 }) 1526 }) 1527 }) 1528 1529 packageC := registry.PackageManifest{PackageName: "PackageC"} 1530 By("Package C", func() { 1531 Step(1, "Default Channel: Stable", func() { 1532 packageC.DefaultChannelName = stableChannel 1533 }) 1534 1535 Step(1, "Channel Stable", func() { 1536 Step(2, "Operator E (Provides: CRD 2)", func() { 1537 csvE := newCSV("csv-e", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd2}, nil, nil) 1538 packageC.Channels = append(packageC.Channels, registry.PackageChannel{Name: stable, CurrentCSVName: csvE.GetName()}) 1539 csvs2 = append(csvs2, csvE) 1540 }) 1541 }) 1542 }) 1543 1544 By(`create catalogsources`) 1545 var catsrc, catsrc2 *operatorsv1alpha1.CatalogSource 1546 var cleanup cleanupFunc 1547 By("creating catalogsources", func() { 1548 var c1, c2 cleanupFunc 1549 catsrc, c1 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc"), generatedNamespace.GetName(), []registry.PackageManifest{testPackage, anotherPackage}, []apiextensionsv1.CustomResourceDefinition{crd, crd2}, csvs1) 1550 catsrc2, c2 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc2"), generatedNamespace.GetName(), []registry.PackageManifest{packageBInCatsrc2, packageC}, []apiextensionsv1.CustomResourceDefinition{crd, crd2}, csvs2) 1551 cleanup = func() { 1552 c1() 1553 c2() 1554 } 1555 }) 1556 defer cleanup() 1557 1558 By("waiting for catalogsources to be ready", func() { 1559 _, err := fetchCatalogSourceOnStatus(crClient, catsrc.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 1560 require.NoError(GinkgoT(), err) 1561 _, err = fetchCatalogSourceOnStatus(crClient, catsrc2.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 1562 require.NoError(GinkgoT(), err) 1563 }) 1564 1565 By(`Create a subscription for test-package in catsrc`) 1566 subscriptionSpec := &operatorsv1alpha1.SubscriptionSpec{ 1567 CatalogSource: catsrc.GetName(), 1568 CatalogSourceNamespace: catsrc.GetNamespace(), 1569 Package: testPackage.PackageName, 1570 Channel: stableChannel, 1571 InstallPlanApproval: operatorsv1alpha1.ApprovalAutomatic, 1572 } 1573 subscriptionName := genName("sub-") 1574 cleanupSubscription := createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subscriptionSpec) 1575 defer cleanupSubscription() 1576 1577 subscription, err := fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) 1578 require.NoError(GinkgoT(), err) 1579 require.NotNil(GinkgoT(), subscription) 1580 1581 By(`ensure correct CSVs were picked`) 1582 var got []string 1583 Eventually(func() []string { 1584 ip, err := crClient.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Get(context.Background(), subscription.Status.InstallPlanRef.Name, metav1.GetOptions{}) 1585 if err != nil { 1586 return nil 1587 } 1588 got = ip.Spec.ClusterServiceVersionNames 1589 return got 1590 }).ShouldNot(BeNil()) 1591 require.ElementsMatch(GinkgoT(), []string{"csv-a", "csv-b", "csv-e"}, got) 1592 }) 1593 1594 Context("to an operator with dependencies from different CatalogSources with priorities", func() { 1595 var ( 1596 kubeClient operatorclient.ClientInterface 1597 crClient versioned.Interface 1598 crd apiextensionsv1.CustomResourceDefinition 1599 packageMain, packageDepRight, packageDepWrong registry.PackageManifest 1600 csvsMain, csvsRight, csvsWrong []operatorsv1alpha1.ClusterServiceVersion 1601 catsrcMain, catsrcDepRight, catsrcDepWrong *operatorsv1alpha1.CatalogSource 1602 cleanup, cleanupSubscription cleanupFunc 1603 ) 1604 const ( 1605 mainCSVName = "csv-main" 1606 rightCSVName = "csv-right" 1607 wrongCSVName = "csv-wrong" 1608 ) 1609 1610 BeforeEach(func() { 1611 kubeClient = ctx.Ctx().KubeClient() 1612 crClient = ctx.Ctx().OperatorClient() 1613 crd = newCRD(genName("ins")) 1614 1615 packageMain = registry.PackageManifest{PackageName: genName("PkgMain-")} 1616 csv := newCSV(mainCSVName, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), nil, 1617 []apiextensionsv1.CustomResourceDefinition{crd}, nil) 1618 packageMain.DefaultChannelName = stableChannel 1619 packageMain.Channels = append(packageMain.Channels, registry.PackageChannel{Name: stableChannel, CurrentCSVName: csv.GetName()}) 1620 1621 csvsMain = []operatorsv1alpha1.ClusterServiceVersion{csv} 1622 csvsRight = []operatorsv1alpha1.ClusterServiceVersion{} 1623 csvsWrong = []operatorsv1alpha1.ClusterServiceVersion{} 1624 }) 1625 1626 Context("creating CatalogSources providing the same dependency with different names", func() { 1627 var catsrcCleanup1, catsrcCleanup2, catsrcCleanup3 cleanupFunc 1628 1629 BeforeEach(func() { 1630 packageDepRight = registry.PackageManifest{PackageName: "PackageDependent"} 1631 csv := newCSV(rightCSVName, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), 1632 []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) 1633 packageDepRight.DefaultChannelName = alphaChannel 1634 packageDepRight.Channels = []registry.PackageChannel{{Name: alphaChannel, CurrentCSVName: csv.GetName()}} 1635 csvsRight = append(csvsRight, csv) 1636 1637 csv.Name = wrongCSVName 1638 packageDepWrong = packageDepRight 1639 packageDepWrong.Channels = []registry.PackageChannel{{Name: alphaChannel, CurrentCSVName: csv.GetName()}} 1640 csvsWrong = append(csvsWrong, csv) 1641 1642 catsrcMain, catsrcCleanup1 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc"), generatedNamespace.GetName(), 1643 []registry.PackageManifest{packageMain}, nil, csvsMain) 1644 1645 catsrcDepRight, catsrcCleanup2 = createInternalCatalogSource(kubeClient, crClient, "catsrc1", generatedNamespace.GetName(), 1646 []registry.PackageManifest{packageDepRight}, []apiextensionsv1.CustomResourceDefinition{crd}, csvsRight) 1647 1648 catsrcDepWrong, catsrcCleanup3 = createInternalCatalogSource(kubeClient, crClient, "catsrc2", generatedNamespace.GetName(), 1649 []registry.PackageManifest{packageDepWrong}, []apiextensionsv1.CustomResourceDefinition{crd}, csvsWrong) 1650 1651 _, err := fetchCatalogSourceOnStatus(crClient, catsrcMain.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 1652 Expect(err).ToNot(HaveOccurred()) 1653 _, err = fetchCatalogSourceOnStatus(crClient, catsrcDepRight.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 1654 Expect(err).ToNot(HaveOccurred()) 1655 _, err = fetchCatalogSourceOnStatus(crClient, catsrcDepWrong.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 1656 Expect(err).ToNot(HaveOccurred()) 1657 }) 1658 1659 AfterEach(func() { 1660 if catsrcCleanup1 != nil { 1661 catsrcCleanup1() 1662 } 1663 if catsrcCleanup2 != nil { 1664 catsrcCleanup2() 1665 } 1666 if catsrcCleanup3 != nil { 1667 catsrcCleanup3() 1668 } 1669 }) 1670 1671 When("creating subscription for the main package", func() { 1672 var subscription *operatorsv1alpha1.Subscription 1673 1674 BeforeEach(func() { 1675 By(`Create a subscription for test-package in catsrc`) 1676 subscriptionSpec := &operatorsv1alpha1.SubscriptionSpec{ 1677 CatalogSource: catsrcMain.GetName(), 1678 CatalogSourceNamespace: catsrcMain.GetNamespace(), 1679 Package: packageMain.PackageName, 1680 Channel: stableChannel, 1681 InstallPlanApproval: operatorsv1alpha1.ApprovalAutomatic, 1682 } 1683 subscriptionName := genName("sub-") 1684 cleanupSubscription = createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subscriptionSpec) 1685 1686 var err error 1687 subscription, err = fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) 1688 Expect(err).ToNot(HaveOccurred()) 1689 Expect(subscription).ToNot(BeNil()) 1690 1691 _, err = fetchCSV(crClient, generatedNamespace.GetName(), mainCSVName, csvSucceededChecker) 1692 Expect(err).ToNot(HaveOccurred()) 1693 1694 }) 1695 1696 AfterEach(func() { 1697 if cleanupSubscription != nil { 1698 cleanupSubscription() 1699 } 1700 if cleanup != nil { 1701 cleanup() 1702 } 1703 }) 1704 1705 It("[FLAKE] choose the dependency from the right CatalogSource based on lexicographical name ordering of catalogs", func() { 1706 By(`ensure correct CSVs were picked`) 1707 Eventually(func() ([]string, error) { 1708 ip, err := crClient.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Get(context.Background(), subscription.Status.InstallPlanRef.Name, metav1.GetOptions{}) 1709 if err != nil { 1710 return nil, err 1711 } 1712 return ip.Spec.ClusterServiceVersionNames, nil 1713 }).Should(ConsistOf(mainCSVName, rightCSVName)) 1714 }) 1715 }) 1716 }) 1717 1718 Context("creating the main and an arbitrary CatalogSources providing the same dependency", func() { 1719 var catsrcCleanup1, catsrcCleanup2 cleanupFunc 1720 1721 BeforeEach(func() { 1722 1723 packageDepRight = registry.PackageManifest{PackageName: "PackageDependent"} 1724 csv := newCSV(rightCSVName, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), 1725 []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) 1726 packageDepRight.DefaultChannelName = alphaChannel 1727 packageDepRight.Channels = []registry.PackageChannel{{Name: alphaChannel, CurrentCSVName: csv.GetName()}} 1728 csvsMain = append(csvsMain, csv) 1729 1730 csv.Name = wrongCSVName 1731 packageDepWrong = packageDepRight 1732 packageDepWrong.Channels = []registry.PackageChannel{{Name: alphaChannel, CurrentCSVName: csv.GetName()}} 1733 csvsWrong = append(csvsWrong, csv) 1734 1735 catsrcMain, catsrcCleanup1 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc"), generatedNamespace.GetName(), 1736 []registry.PackageManifest{packageDepRight, packageMain}, []apiextensionsv1.CustomResourceDefinition{crd}, csvsMain) 1737 1738 catsrcDepWrong, catsrcCleanup2 = createInternalCatalogSourceWithPriority(kubeClient, crClient, 1739 genName("catsrc"), generatedNamespace.GetName(), []registry.PackageManifest{packageDepWrong}, []apiextensionsv1.CustomResourceDefinition{crd}, 1740 csvsWrong, 100) 1741 1742 By(`waiting for catalogsources to be ready`) 1743 _, err := fetchCatalogSourceOnStatus(crClient, catsrcMain.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 1744 Expect(err).ToNot(HaveOccurred()) 1745 _, err = fetchCatalogSourceOnStatus(crClient, catsrcDepWrong.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 1746 Expect(err).ToNot(HaveOccurred()) 1747 }) 1748 1749 AfterEach(func() { 1750 if catsrcCleanup1 != nil { 1751 catsrcCleanup1() 1752 } 1753 if catsrcCleanup2 != nil { 1754 catsrcCleanup2() 1755 } 1756 1757 }) 1758 1759 When("creating subscription for the main package", func() { 1760 var subscription *operatorsv1alpha1.Subscription 1761 1762 BeforeEach(func() { 1763 By(`Create a subscription for test-package in catsrc`) 1764 subscriptionSpec := &operatorsv1alpha1.SubscriptionSpec{ 1765 CatalogSource: catsrcMain.GetName(), 1766 CatalogSourceNamespace: catsrcMain.GetNamespace(), 1767 Package: packageMain.PackageName, 1768 Channel: stableChannel, 1769 InstallPlanApproval: operatorsv1alpha1.ApprovalAutomatic, 1770 } 1771 subscriptionName := genName("sub-") 1772 cleanupSubscription = createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subscriptionSpec) 1773 1774 var err error 1775 subscription, err = fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) 1776 Expect(err).ToNot(HaveOccurred()) 1777 Expect(subscription).ToNot(BeNil()) 1778 1779 _, err = fetchCSV(crClient, generatedNamespace.GetName(), mainCSVName, csvSucceededChecker) 1780 Expect(err).ToNot(HaveOccurred()) 1781 1782 }) 1783 1784 AfterEach(func() { 1785 if cleanupSubscription != nil { 1786 cleanupSubscription() 1787 } 1788 if cleanup != nil { 1789 cleanup() 1790 } 1791 }) 1792 1793 It("choose the dependent package from the same catsrc as the installing operator", func() { 1794 By(`ensure correct CSVs were picked`) 1795 Eventually(func() ([]string, error) { 1796 ip, err := crClient.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Get(context.Background(), subscription.Status.InstallPlanRef.Name, metav1.GetOptions{}) 1797 if err != nil { 1798 return nil, err 1799 } 1800 return ip.Spec.ClusterServiceVersionNames, nil 1801 }).Should(ConsistOf(mainCSVName, rightCSVName)) 1802 }) 1803 }) 1804 }) 1805 1806 Context("creating CatalogSources providing the same dependency with different priority value", func() { 1807 var catsrcCleanup1, catsrcCleanup2, catsrcCleanup3 cleanupFunc 1808 1809 BeforeEach(func() { 1810 packageDepRight = registry.PackageManifest{PackageName: "PackageDependent"} 1811 csv := newCSV(rightCSVName, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), 1812 []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) 1813 packageDepRight.DefaultChannelName = alphaChannel 1814 packageDepRight.Channels = []registry.PackageChannel{{Name: alphaChannel, CurrentCSVName: csv.GetName()}} 1815 csvsRight = append(csvsRight, csv) 1816 1817 csv.Name = wrongCSVName 1818 packageDepWrong = packageDepRight 1819 packageDepWrong.Channels = []registry.PackageChannel{{Name: alphaChannel, CurrentCSVName: csv.GetName()}} 1820 csvsWrong = append(csvsWrong, csv) 1821 1822 catsrcMain, catsrcCleanup1 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc"), generatedNamespace.GetName(), 1823 []registry.PackageManifest{packageMain}, nil, csvsMain) 1824 1825 catsrcDepRight, catsrcCleanup2 = createInternalCatalogSourceWithPriority(kubeClient, crClient, 1826 genName("catsrc"), generatedNamespace.GetName(), []registry.PackageManifest{packageDepRight}, []apiextensionsv1.CustomResourceDefinition{crd}, 1827 csvsRight, 100) 1828 1829 catsrcDepWrong, catsrcCleanup3 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc"), generatedNamespace.GetName(), 1830 []registry.PackageManifest{packageDepWrong}, []apiextensionsv1.CustomResourceDefinition{crd}, csvsWrong) 1831 1832 By(`waiting for catalogsources to be ready`) 1833 _, err := fetchCatalogSourceOnStatus(crClient, catsrcMain.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 1834 Expect(err).ToNot(HaveOccurred()) 1835 _, err = fetchCatalogSourceOnStatus(crClient, catsrcDepRight.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 1836 Expect(err).ToNot(HaveOccurred()) 1837 _, err = fetchCatalogSourceOnStatus(crClient, catsrcDepWrong.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 1838 Expect(err).ToNot(HaveOccurred()) 1839 }) 1840 1841 AfterEach(func() { 1842 if catsrcCleanup1 != nil { 1843 catsrcCleanup1() 1844 } 1845 if catsrcCleanup2 != nil { 1846 catsrcCleanup2() 1847 } 1848 if catsrcCleanup3 != nil { 1849 catsrcCleanup3() 1850 } 1851 }) 1852 1853 When("creating subscription for the main package", func() { 1854 var subscription *operatorsv1alpha1.Subscription 1855 1856 BeforeEach(func() { 1857 By(`Create a subscription for test-package in catsrc`) 1858 subscriptionSpec := &operatorsv1alpha1.SubscriptionSpec{ 1859 CatalogSource: catsrcMain.GetName(), 1860 CatalogSourceNamespace: catsrcMain.GetNamespace(), 1861 Package: packageMain.PackageName, 1862 Channel: stableChannel, 1863 InstallPlanApproval: operatorsv1alpha1.ApprovalAutomatic, 1864 } 1865 subscriptionName := genName("sub-") 1866 cleanupSubscription = createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subscriptionSpec) 1867 1868 var err error 1869 subscription, err = fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) 1870 Expect(err).ToNot(HaveOccurred()) 1871 Expect(subscription).ToNot(BeNil()) 1872 1873 _, err = fetchCSV(crClient, generatedNamespace.GetName(), mainCSVName, csvSucceededChecker) 1874 Expect(err).ToNot(HaveOccurred()) 1875 1876 }) 1877 1878 AfterEach(func() { 1879 if cleanupSubscription != nil { 1880 cleanupSubscription() 1881 } 1882 if cleanup != nil { 1883 cleanup() 1884 } 1885 }) 1886 1887 It("choose the dependent package from the catsrc with higher priority", func() { 1888 By(`ensure correct CSVs were picked`) 1889 Eventually(func() ([]string, error) { 1890 ip, err := crClient.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Get(context.Background(), subscription.Status.InstallPlanRef.Name, metav1.GetOptions{}) 1891 if err != nil { 1892 return nil, err 1893 } 1894 return ip.Spec.ClusterServiceVersionNames, nil 1895 }).Should(ConsistOf(mainCSVName, rightCSVName)) 1896 }) 1897 }) 1898 }) 1899 1900 Context("creating CatalogSources providing the same dependency under test and global namespaces", func() { 1901 var catsrcCleanup1, catsrcCleanup2, catsrcCleanup3 cleanupFunc 1902 1903 BeforeEach(func() { 1904 1905 packageDepRight = registry.PackageManifest{PackageName: "PackageDependent"} 1906 csv := newCSV(rightCSVName, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), 1907 []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) 1908 packageDepRight.DefaultChannelName = alphaChannel 1909 packageDepRight.Channels = []registry.PackageChannel{{Name: alphaChannel, CurrentCSVName: csv.GetName()}} 1910 csvsRight = append(csvsRight, csv) 1911 1912 csv.Name = wrongCSVName 1913 packageDepWrong = packageDepRight 1914 packageDepWrong.Channels = []registry.PackageChannel{{Name: alphaChannel, CurrentCSVName: csv.GetName()}} 1915 csvsWrong = append(csvsWrong, csv) 1916 1917 catsrcMain, catsrcCleanup1 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc"), generatedNamespace.GetName(), 1918 []registry.PackageManifest{packageMain}, nil, csvsMain) 1919 1920 catsrcDepRight, catsrcCleanup2 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc"), generatedNamespace.GetName(), 1921 []registry.PackageManifest{packageDepRight}, []apiextensionsv1.CustomResourceDefinition{crd}, csvsRight) 1922 1923 catsrcDepWrong, catsrcCleanup3 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc"), operatorNamespace, 1924 []registry.PackageManifest{packageDepWrong}, []apiextensionsv1.CustomResourceDefinition{crd}, csvsWrong) 1925 1926 By(`waiting for catalogsources to be ready`) 1927 _, err := fetchCatalogSourceOnStatus(crClient, catsrcMain.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 1928 Expect(err).ToNot(HaveOccurred()) 1929 _, err = fetchCatalogSourceOnStatus(crClient, catsrcDepRight.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 1930 Expect(err).ToNot(HaveOccurred()) 1931 _, err = fetchCatalogSourceOnStatus(crClient, catsrcDepWrong.GetName(), operatorNamespace, catalogSourceRegistryPodSynced()) 1932 Expect(err).ToNot(HaveOccurred()) 1933 }) 1934 1935 AfterEach(func() { 1936 if catsrcCleanup1 != nil { 1937 catsrcCleanup1() 1938 } 1939 if catsrcCleanup2 != nil { 1940 catsrcCleanup2() 1941 } 1942 if catsrcCleanup3 != nil { 1943 catsrcCleanup3() 1944 } 1945 }) 1946 1947 When("creating subscription for the main package", func() { 1948 var subscription *operatorsv1alpha1.Subscription 1949 1950 BeforeEach(func() { 1951 By(`Create a subscription for test-package in catsrc`) 1952 subscriptionSpec := &operatorsv1alpha1.SubscriptionSpec{ 1953 CatalogSource: catsrcMain.GetName(), 1954 CatalogSourceNamespace: catsrcMain.GetNamespace(), 1955 Package: packageMain.PackageName, 1956 Channel: stableChannel, 1957 InstallPlanApproval: operatorsv1alpha1.ApprovalAutomatic, 1958 } 1959 subscriptionName := genName("sub-") 1960 cleanupSubscription = createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subscriptionSpec) 1961 1962 var err error 1963 subscription, err = fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) 1964 Expect(err).ToNot(HaveOccurred()) 1965 Expect(subscription).ToNot(BeNil()) 1966 1967 _, err = fetchCSV(crClient, generatedNamespace.GetName(), mainCSVName, csvSucceededChecker) 1968 Expect(err).ToNot(HaveOccurred()) 1969 1970 }) 1971 1972 AfterEach(func() { 1973 if cleanupSubscription != nil { 1974 cleanupSubscription() 1975 } 1976 if cleanup != nil { 1977 cleanup() 1978 } 1979 }) 1980 1981 It("choose the dependent package from the catsrc in the same namespace as the installing operator", func() { 1982 By(`ensure correct CSVs were picked`) 1983 Eventually(func() ([]string, error) { 1984 ip, err := crClient.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Get(context.Background(), subscription.Status.InstallPlanRef.Name, metav1.GetOptions{}) 1985 if err != nil { 1986 return nil, err 1987 } 1988 return ip.Spec.ClusterServiceVersionNames, nil 1989 }).Should(ConsistOf(mainCSVName, rightCSVName)) 1990 }) 1991 }) 1992 }) 1993 }) 1994 1995 It("creation in case of transferring providedAPIs", func() { 1996 By(`csvA owns CRD1 & csvB owns CRD2 and requires CRD1`) 1997 By(`Create subscription for csvB lead to installation of csvB and csvA`) 1998 By(`Update catsrc to upgrade csvA to csvNewA which now requires CRD1`) 1999 By(`csvNewA can't be installed due to no other operators provide CRD1 for it`) 2000 By(`(Note: OLM can't pick csvA as dependency for csvNewA as it is from the same`) 2001 By(`same package)`) 2002 By(`Update catsrc again to upgrade csvB to csvNewB which now owns both CRD1 and`) 2003 By(`CRD2.`) 2004 By(`Now csvNewA and csvNewB are installed successfully as csvNewB provides CRD1`) 2005 By(`that csvNewA requires`) 2006 By(` PARITY: this test covers the same scenario as the TestSolveOperators_TransferApiOwnership unit test`) 2007 kubeClient := ctx.Ctx().KubeClient() 2008 crClient := ctx.Ctx().OperatorClient() 2009 2010 crd := newCRD(genName("ins")) 2011 crd2 := newCRD(genName("ins")) 2012 2013 By(`Create CSV`) 2014 packageName1 := genName("apackage") 2015 packageName2 := genName("bpackage") 2016 2017 By(`csvA provides CRD`) 2018 csvA := newCSV("nginx-a", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) 2019 By(`csvB provides CRD2 and requires CRD`) 2020 csvB := newCSV("nginx-b", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd2}, []apiextensionsv1.CustomResourceDefinition{crd}, nil) 2021 By(`New csvA requires CRD (transfer CRD ownership to the new csvB)`) 2022 csvNewA := newCSV("nginx-new-a", generatedNamespace.GetName(), "nginx-a", semver.MustParse("0.2.0"), nil, []apiextensionsv1.CustomResourceDefinition{crd}, nil) 2023 By(`New csvB provides CRD and CRD2`) 2024 csvNewB := newCSV("nginx-new-b", generatedNamespace.GetName(), "nginx-b", semver.MustParse("0.2.0"), []apiextensionsv1.CustomResourceDefinition{crd, crd2}, nil, nil) 2025 2026 By(`constraints not satisfiable:`) 2027 By(`apackagert6cq requires at least one of catsrcc6xgr/operators/stable/nginx-new-a,`) 2028 By(`apackagert6cq is mandatory,`) 2029 By(`pkgunique/apackagert6cq permits at most 1 of catsrcc6xgr/operators/stable/nginx-new-a, catsrcc6xgr/operators/stable/nginx-a,`) 2030 By(`catsrcc6xgr/operators/stable/nginx-new-a requires at least one of catsrcc6xgr/operators/stable/nginx-a`) 2031 2032 By(`Create PackageManifests 1`) 2033 By(`Contain csvA, ABC and B`) 2034 manifests := []registry.PackageManifest{ 2035 { 2036 PackageName: packageName1, 2037 Channels: []registry.PackageChannel{ 2038 {Name: stableChannel, CurrentCSVName: csvA.GetName()}, 2039 }, 2040 DefaultChannelName: stableChannel, 2041 }, 2042 { 2043 PackageName: packageName2, 2044 Channels: []registry.PackageChannel{ 2045 {Name: stableChannel, CurrentCSVName: csvB.GetName()}, 2046 }, 2047 DefaultChannelName: stableChannel, 2048 }, 2049 } 2050 2051 catalogSourceName := genName("catsrc") 2052 catsrc, cleanup := createInternalCatalogSource(kubeClient, crClient, catalogSourceName, generatedNamespace.GetName(), manifests, []apiextensionsv1.CustomResourceDefinition{crd, crd2}, []operatorsv1alpha1.ClusterServiceVersion{csvA, csvB}) 2053 defer cleanup() 2054 2055 By(`Ensure that the catalog source is resolved before we create a subscription.`) 2056 _, err := fetchCatalogSourceOnStatus(crClient, catsrc.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 2057 require.NoError(GinkgoT(), err) 2058 2059 subscriptionSpec := &operatorsv1alpha1.SubscriptionSpec{ 2060 CatalogSource: catsrc.GetName(), 2061 CatalogSourceNamespace: catsrc.GetNamespace(), 2062 Package: packageName2, 2063 Channel: stableChannel, 2064 StartingCSV: csvB.GetName(), 2065 InstallPlanApproval: operatorsv1alpha1.ApprovalAutomatic, 2066 } 2067 2068 By(`Create a subscription that has a dependency`) 2069 subscriptionName := genName("sub-") 2070 cleanupSubscription := createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subscriptionSpec) 2071 defer cleanupSubscription() 2072 2073 subscription, err := fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionStateAtLatestChecker()) 2074 require.NoError(GinkgoT(), err) 2075 require.NotNil(GinkgoT(), subscription) 2076 2077 By(`Check that a single catalog source was used to resolve the InstallPlan`) 2078 _, err = fetchInstallPlanWithNamespace(GinkgoT(), crClient, subscription.Status.InstallPlanRef.Name, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete)) 2079 require.NoError(GinkgoT(), err) 2080 By(`Fetch CSVs A and B`) 2081 _, err = fetchCSV(crClient, generatedNamespace.GetName(), csvA.Name, csvSucceededChecker) 2082 require.NoError(GinkgoT(), err) 2083 _, err = fetchCSV(crClient, generatedNamespace.GetName(), csvB.Name, csvSucceededChecker) 2084 require.NoError(GinkgoT(), err) 2085 2086 By(`Update PackageManifest`) 2087 manifests = []registry.PackageManifest{ 2088 { 2089 PackageName: packageName1, 2090 Channels: []registry.PackageChannel{ 2091 {Name: stableChannel, CurrentCSVName: csvNewA.GetName()}, 2092 }, 2093 DefaultChannelName: stableChannel, 2094 }, 2095 { 2096 PackageName: packageName2, 2097 Channels: []registry.PackageChannel{ 2098 {Name: stableChannel, CurrentCSVName: csvB.GetName()}, 2099 }, 2100 DefaultChannelName: stableChannel, 2101 }, 2102 } 2103 updateInternalCatalog(GinkgoT(), kubeClient, crClient, catalogSourceName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{crd, crd2}, []operatorsv1alpha1.ClusterServiceVersion{csvNewA, csvA, csvB}, manifests) 2104 csvAsub := strings.Join([]string{packageName1, stableChannel, catalogSourceName, generatedNamespace.GetName()}, "-") 2105 _, err = fetchSubscription(crClient, generatedNamespace.GetName(), csvAsub, subscriptionStateAtLatestChecker()) 2106 require.NoError(GinkgoT(), err) 2107 By(`Ensure csvNewA is not installed`) 2108 _, err = crClient.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Get(context.Background(), csvNewA.Name, metav1.GetOptions{}) 2109 require.Error(GinkgoT(), err) 2110 By(`Ensure csvA still exists`) 2111 _, err = fetchCSV(crClient, generatedNamespace.GetName(), csvA.Name, csvSucceededChecker) 2112 require.NoError(GinkgoT(), err) 2113 2114 By(`Update packagemanifest again`) 2115 manifests = []registry.PackageManifest{ 2116 { 2117 PackageName: packageName1, 2118 Channels: []registry.PackageChannel{ 2119 {Name: stableChannel, CurrentCSVName: csvNewA.GetName()}, 2120 }, 2121 DefaultChannelName: stableChannel, 2122 }, 2123 { 2124 PackageName: packageName2, 2125 Channels: []registry.PackageChannel{ 2126 {Name: stableChannel, CurrentCSVName: csvNewB.GetName()}, 2127 }, 2128 DefaultChannelName: stableChannel, 2129 }, 2130 } 2131 updateInternalCatalog(GinkgoT(), kubeClient, crClient, catalogSourceName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{crd, crd2}, []operatorsv1alpha1.ClusterServiceVersion{csvA, csvB, csvNewA, csvNewB}, manifests) 2132 2133 _, err = fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanDifferentChecker(subscription.Status.InstallPlanRef.Name)) 2134 require.NoError(GinkgoT(), err) 2135 By(`Ensure csvNewA is installed`) 2136 _, err = fetchCSV(crClient, generatedNamespace.GetName(), csvNewA.Name, csvSucceededChecker) 2137 require.NoError(GinkgoT(), err) 2138 By(`Ensure csvNewB is installed`) 2139 _, err = fetchCSV(crClient, generatedNamespace.GetName(), csvNewB.Name, csvSucceededChecker) 2140 require.NoError(GinkgoT(), err) 2141 }) 2142 2143 When("A subscription is created for an operator that requires an API that is not available", func() { 2144 var ( 2145 c operatorclient.ClientInterface 2146 crc versioned.Interface 2147 teardown func() 2148 cleanup func() 2149 packages []registry.PackageManifest 2150 crd = newCRD(genName("foo-")) 2151 csvA operatorsv1alpha1.ClusterServiceVersion 2152 csvB operatorsv1alpha1.ClusterServiceVersion 2153 subName = genName("test-subscription-") 2154 catSrcName = genName("test-catalog-") 2155 ) 2156 2157 BeforeEach(func() { 2158 c = newKubeClient() 2159 crc = newCRClient() 2160 2161 packages = []registry.PackageManifest{ 2162 { 2163 PackageName: "test-package", 2164 Channels: []registry.PackageChannel{ 2165 {Name: "alpha", CurrentCSVName: "csvA"}, 2166 }, 2167 DefaultChannelName: "alpha", 2168 }, 2169 } 2170 csvA = newCSV("csvA", generatedNamespace.GetName(), "", semver.MustParse("1.0.0"), nil, []apiextensionsv1.CustomResourceDefinition{crd}, nil) 2171 2172 _, teardown = createInternalCatalogSource(c, ctx.Ctx().OperatorClient(), catSrcName, generatedNamespace.GetName(), packages, nil, []operatorsv1alpha1.ClusterServiceVersion{csvA}) 2173 2174 By(`Ensure that the catalog source is resolved before we create a subscription.`) 2175 _, err := fetchCatalogSourceOnStatus(crc, catSrcName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 2176 require.NoError(GinkgoT(), err) 2177 2178 cleanup = createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subName, catSrcName, "test-package", "alpha", "", operatorsv1alpha1.ApprovalAutomatic) 2179 }) 2180 2181 AfterEach(func() { 2182 cleanup() 2183 teardown() 2184 }) 2185 2186 It("the subscription has a condition in it's status that indicates the resolution error", func() { 2187 Eventually(func() (corev1.ConditionStatus, error) { 2188 sub, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), subName, metav1.GetOptions{}) 2189 if err != nil { 2190 return corev1.ConditionUnknown, err 2191 } 2192 return sub.Status.GetCondition(operatorsv1alpha1.SubscriptionResolutionFailed).Status, nil 2193 }).Should(Equal(corev1.ConditionTrue)) 2194 }) 2195 2196 When("the required API is made available", func() { 2197 2198 BeforeEach(func() { 2199 newPkg := registry.PackageManifest{ 2200 PackageName: "another-package", 2201 Channels: []registry.PackageChannel{ 2202 {Name: "alpha", CurrentCSVName: "csvB"}, 2203 }, 2204 DefaultChannelName: "alpha", 2205 } 2206 packages = append(packages, newPkg) 2207 2208 csvB = newCSV("csvB", generatedNamespace.GetName(), "", semver.MustParse("1.0.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) 2209 2210 updateInternalCatalog(GinkgoT(), c, crc, catSrcName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{csvA, csvB}, packages) 2211 }) 2212 2213 It("the ResolutionFailed condition previously set in its status that indicated the resolution error is cleared off", func() { 2214 Eventually(func() (corev1.ConditionStatus, error) { 2215 sub, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), subName, metav1.GetOptions{}) 2216 if err != nil { 2217 return corev1.ConditionFalse, err 2218 } 2219 return sub.Status.GetCondition(operatorsv1alpha1.SubscriptionResolutionFailed).Status, nil 2220 }).Should(Equal(corev1.ConditionUnknown)) 2221 }) 2222 }) 2223 }) 2224 2225 When("an unannotated ClusterServiceVersion exists with an associated Subscription", func() { 2226 var ( 2227 teardown func() 2228 ) 2229 2230 BeforeEach(func() { 2231 teardown = func() {} 2232 2233 packages := []registry.PackageManifest{ 2234 { 2235 PackageName: "package", 2236 Channels: []registry.PackageChannel{ 2237 {Name: "channel-x", CurrentCSVName: "csv-x"}, 2238 {Name: "channel-y", CurrentCSVName: "csv-y"}, 2239 }, 2240 DefaultChannelName: "channel-x", 2241 }, 2242 } 2243 2244 x := newCSV("csv-x", generatedNamespace.GetName(), "", semver.MustParse("1.0.0"), nil, nil, nil) 2245 y := newCSV("csv-y", generatedNamespace.GetName(), "", semver.MustParse("1.0.0"), nil, nil, nil) 2246 2247 _, teardown = createInternalCatalogSource(ctx.Ctx().KubeClient(), ctx.Ctx().OperatorClient(), "test-catalog", generatedNamespace.GetName(), packages, nil, []operatorsv1alpha1.ClusterServiceVersion{x, y}) 2248 2249 createSubscriptionForCatalog(ctx.Ctx().OperatorClient(), generatedNamespace.GetName(), "test-subscription-x", "test-catalog", "package", "channel-x", "", operatorsv1alpha1.ApprovalAutomatic) 2250 2251 Eventually(func() error { 2252 var unannotated operatorsv1alpha1.ClusterServiceVersion 2253 if err := ctx.Ctx().Client().Get(context.Background(), client.ObjectKey{Namespace: generatedNamespace.GetName(), Name: "csv-x"}, &unannotated); err != nil { 2254 return err 2255 } 2256 if _, ok := unannotated.Annotations["operatorframework.io/properties"]; !ok { 2257 return nil 2258 } 2259 delete(unannotated.Annotations, "operatorframework.io/properties") 2260 return ctx.Ctx().Client().Update(context.Background(), &unannotated) 2261 }).Should(Succeed()) 2262 }) 2263 2264 AfterEach(func() { 2265 teardown() 2266 }) 2267 2268 It("uses inferred properties to prevent a duplicate installation from the same package ", func() { 2269 createSubscriptionForCatalog(ctx.Ctx().OperatorClient(), generatedNamespace.GetName(), "test-subscription-y", "test-catalog", "package", "channel-y", "", operatorsv1alpha1.ApprovalAutomatic) 2270 2271 Consistently(func() error { 2272 var no operatorsv1alpha1.ClusterServiceVersion 2273 return ctx.Ctx().Client().Get(context.Background(), client.ObjectKey{Namespace: generatedNamespace.GetName(), Name: "csv-y"}, &no) 2274 }).ShouldNot(Succeed()) 2275 }) 2276 }) 2277 2278 When("there exists a Subscription to an operator having dependency candidates in both default and nondefault channels", func() { 2279 var ( 2280 teardown func() 2281 ) 2282 2283 BeforeEach(func() { 2284 teardown = func() {} 2285 2286 packages := []registry.PackageManifest{ 2287 { 2288 PackageName: "dependency", 2289 Channels: []registry.PackageChannel{ 2290 {Name: "default", CurrentCSVName: "csv-dependency"}, 2291 {Name: "nondefault", CurrentCSVName: "csv-dependency"}, 2292 }, 2293 DefaultChannelName: "default", 2294 }, 2295 { 2296 PackageName: "root", 2297 Channels: []registry.PackageChannel{ 2298 {Name: "unimportant", CurrentCSVName: "csv-root"}, 2299 }, 2300 DefaultChannelName: "unimportant", 2301 }, 2302 } 2303 2304 crds := []apiextensionsv1.CustomResourceDefinition{newCRD(genName("crd-"))} 2305 csvs := []operatorsv1alpha1.ClusterServiceVersion{ 2306 newCSV("csv-dependency", generatedNamespace.GetName(), "", semver.MustParse("1.0.0"), crds, nil, nil), 2307 newCSV("csv-root", generatedNamespace.GetName(), "", semver.MustParse("1.0.0"), nil, crds, nil), 2308 } 2309 2310 _, teardown = createInternalCatalogSource(ctx.Ctx().KubeClient(), ctx.Ctx().OperatorClient(), "test-catalog", generatedNamespace.GetName(), packages, crds, csvs) 2311 2312 createSubscriptionForCatalog(ctx.Ctx().OperatorClient(), generatedNamespace.GetName(), "test-subscription", "test-catalog", "root", "unimportant", "", operatorsv1alpha1.ApprovalAutomatic) 2313 }) 2314 2315 AfterEach(func() { 2316 teardown() 2317 }) 2318 2319 It("should create a Subscription using the candidate's default channel", func() { 2320 Eventually(func() ([]operatorsv1alpha1.Subscription, error) { 2321 var list operatorsv1alpha1.SubscriptionList 2322 if err := ctx.Ctx().Client().List(context.Background(), &list); err != nil { 2323 return nil, err 2324 } 2325 return list.Items, nil 2326 }).Should(ContainElement(WithTransform( 2327 func(in operatorsv1alpha1.Subscription) operatorsv1alpha1.SubscriptionSpec { 2328 return operatorsv1alpha1.SubscriptionSpec{ 2329 CatalogSource: in.Spec.CatalogSource, 2330 CatalogSourceNamespace: in.Spec.CatalogSourceNamespace, 2331 Package: in.Spec.Package, 2332 Channel: in.Spec.Channel, 2333 } 2334 }, 2335 Equal(operatorsv1alpha1.SubscriptionSpec{ 2336 CatalogSource: "test-catalog", 2337 CatalogSourceNamespace: generatedNamespace.GetName(), 2338 Package: "dependency", 2339 Channel: "default", 2340 }), 2341 ))) 2342 }) 2343 }) 2344 2345 It("unpacks bundle image", func() { 2346 catsrc := &operatorsv1alpha1.CatalogSource{ 2347 ObjectMeta: metav1.ObjectMeta{ 2348 Name: genName("kiali-"), 2349 Namespace: generatedNamespace.GetName(), 2350 Labels: map[string]string{"olm.catalogSource": "kaili-catalog"}, 2351 }, 2352 Spec: operatorsv1alpha1.CatalogSourceSpec{ 2353 Image: "quay.io/operator-framework/ci-index:latest", 2354 SourceType: operatorsv1alpha1.SourceTypeGrpc, 2355 GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ 2356 SecurityContextConfig: operatorsv1alpha1.Restricted, 2357 }, 2358 }, 2359 } 2360 catsrc, err := crc.OperatorsV1alpha1().CatalogSources(catsrc.GetNamespace()).Create(context.Background(), catsrc, metav1.CreateOptions{}) 2361 require.NoError(GinkgoT(), err) 2362 defer func() { 2363 Eventually(func() error { 2364 return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), catsrc)) 2365 }).Should(Succeed()) 2366 }() 2367 2368 By("waiting for the CatalogSource to be ready") 2369 catsrc, err = fetchCatalogSourceOnStatus(crc, catsrc.GetName(), catsrc.GetNamespace(), catalogSourceRegistryPodSynced()) 2370 require.NoError(GinkgoT(), err) 2371 2372 By("generating a Subscription") 2373 subName := genName("kiali-") 2374 cleanUpSubscriptionFn := createSubscriptionForCatalog(crc, catsrc.GetNamespace(), subName, catsrc.GetName(), "kiali", stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) 2375 defer cleanUpSubscriptionFn() 2376 2377 By("waiting for the InstallPlan to get created for the subscription") 2378 sub, err := fetchSubscription(crc, catsrc.GetNamespace(), subName, subscriptionHasInstallPlanChecker()) 2379 require.NoError(GinkgoT(), err) 2380 2381 By("waiting for the expected InstallPlan's execution to either fail or succeed") 2382 ipName := sub.Status.InstallPlanRef.Name 2383 ip, err := waitForInstallPlan(crc, ipName, sub.GetNamespace(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseFailed, operatorsv1alpha1.InstallPlanPhaseComplete)) 2384 require.NoError(GinkgoT(), err) 2385 require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, ip.Status.Phase, "InstallPlan not complete") 2386 2387 By("ensuring the InstallPlan contains the steps resolved from the bundle image") 2388 operatorName := "kiali-operator" 2389 expectedSteps := map[registry.ResourceKey]struct{}{ 2390 {Name: operatorName, Kind: "ClusterServiceVersion"}: {}, 2391 {Name: "kialis.kiali.io", Kind: "CustomResourceDefinition"}: {}, 2392 {Name: "monitoringdashboards.monitoring.kiali.io", Kind: "CustomResourceDefinition"}: {}, 2393 {Name: operatorName, Kind: "ServiceAccount"}: {}, 2394 {Name: operatorName, Kind: "ClusterRole"}: {}, 2395 {Name: operatorName, Kind: "ClusterRoleBinding"}: {}, 2396 } 2397 require.Lenf(GinkgoT(), ip.Status.Plan, len(expectedSteps), "number of expected steps does not match installed: %v", ip.Status.Plan) 2398 2399 for _, step := range ip.Status.Plan { 2400 key := registry.ResourceKey{ 2401 Name: step.Resource.Name, 2402 Kind: step.Resource.Kind, 2403 } 2404 for expected := range expectedSteps { 2405 if strings.HasPrefix(key.Name, expected.Name) && key.Kind == expected.Kind { 2406 delete(expectedSteps, expected) 2407 } 2408 } 2409 } 2410 require.Lenf(GinkgoT(), expectedSteps, 0, "Actual resource steps do not match expected: %#v", expectedSteps) 2411 }) 2412 2413 When("unpacking bundle", func() { 2414 var ( 2415 magicCatalog *MagicCatalog 2416 catalogSourceName string 2417 subName string 2418 ) 2419 2420 BeforeEach(func() { 2421 By("deploying the testing catalog") 2422 provider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.1.0.yaml")) 2423 Expect(err).To(BeNil()) 2424 catalogSourceName = fmt.Sprintf("%s-catsrc", generatedNamespace.GetName()) 2425 magicCatalog = NewMagicCatalog(ctx.Ctx().Client(), generatedNamespace.GetName(), catalogSourceName, provider) 2426 Expect(magicCatalog.DeployCatalog(context.Background())).To(BeNil()) 2427 2428 By("creating the testing subscription") 2429 subName = fmt.Sprintf("%s-test-package-sub", generatedNamespace.GetName()) 2430 createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subName, catalogSourceName, "test-package", stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) 2431 2432 By("waiting until the subscription has an IP reference") 2433 subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionHasInstallPlanChecker()) 2434 Expect(err).Should(BeNil()) 2435 2436 By("waiting for the v0.1.0 CSV to report a succeeded phase") 2437 _, err = fetchCSV(crc, generatedNamespace.GetName(), subscription.Status.CurrentCSV, buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded)) 2438 Expect(err).ShouldNot(HaveOccurred()) 2439 }) 2440 2441 It("should not report unpacking progress or errors after successfull unpacking", func() { 2442 By("verifying that the subscription is not reporting unpacking progress") 2443 Eventually( 2444 func() (corev1.ConditionStatus, error) { 2445 fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), subName, metav1.GetOptions{}) 2446 if err != nil { 2447 return "", err 2448 } 2449 cond := fetched.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleUnpacking) 2450 return cond.Status, nil 2451 }, 2452 5*time.Minute, 2453 interval, 2454 ).Should(Equal(corev1.ConditionUnknown)) 2455 2456 By("verifying that the subscription is not reporting unpacking errors") 2457 Eventually( 2458 func() (corev1.ConditionStatus, error) { 2459 fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), subName, metav1.GetOptions{}) 2460 if err != nil { 2461 return "", err 2462 } 2463 cond := fetched.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleUnpackFailed) 2464 return cond.Status, nil 2465 }, 2466 5*time.Minute, 2467 interval, 2468 ).Should(Equal(corev1.ConditionUnknown)) 2469 }) 2470 2471 Context("with bundle which OLM will fail to unpack", func() { 2472 BeforeEach(func() { 2473 By("patching the OperatorGroup to reduce the bundle unpacking timeout") 2474 ogNN := types.NamespacedName{Name: operatorGroup.GetName(), Namespace: generatedNamespace.GetName()} 2475 addBundleUnpackTimeoutOGAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "1s") 2476 2477 By("updating the catalog with a broken v0.2.0 bundle image") 2478 brokenProvider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.2.0-non-existent-tag.yaml")) 2479 Expect(err).To(BeNil()) 2480 err = magicCatalog.UpdateCatalog(context.Background(), brokenProvider) 2481 Expect(err).To(BeNil()) 2482 }) 2483 2484 It("should expose a condition indicating failure to unpack", func() { 2485 By("verifying that the subscription is reporting bundle unpack failure condition") 2486 Eventually( 2487 func() (string, error) { 2488 fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), subName, metav1.GetOptions{}) 2489 if err != nil { 2490 return "", err 2491 } 2492 cond := fetched.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleUnpackFailed) 2493 if cond.Status != corev1.ConditionTrue || cond.Reason != "BundleUnpackFailed" { 2494 return "", fmt.Errorf("%s condition not found", operatorsv1alpha1.SubscriptionBundleUnpackFailed) 2495 } 2496 2497 return cond.Message, nil 2498 }, 2499 5*time.Minute, 2500 interval, 2501 ).Should(ContainSubstring("bundle unpacking failed. Reason: DeadlineExceeded")) 2502 2503 By("waiting for the subscription to maintain the example-operator.v0.1.0 status.currentCSV") 2504 Consistently(subscriptionCurrentCSVGetter(crc, generatedNamespace.GetName(), subName)).Should(Equal("example-operator.v0.1.0")) 2505 }) 2506 2507 It("should be able to recover when catalog gets updated with a fixed version", func() { 2508 By("patching the OperatorGroup to reduce the bundle unpacking timeout") 2509 ogNN := types.NamespacedName{Name: operatorGroup.GetName(), Namespace: generatedNamespace.GetName()} 2510 addBundleUnpackTimeoutOGAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "5m") 2511 2512 By("updating the catalog with a fixed v0.2.0 bundle image") 2513 brokenProvider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.2.0.yaml")) 2514 Expect(err).To(BeNil()) 2515 err = magicCatalog.UpdateCatalog(context.Background(), brokenProvider) 2516 Expect(err).To(BeNil()) 2517 2518 By("waiting for the subscription to have v0.2.0 installed") 2519 _, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionHasCurrentCSV("example-operator.v0.2.0")) 2520 Expect(err).Should(BeNil()) 2521 }) 2522 2523 It("should report deprecation conditions when package, channel, and bundle are referenced in an olm.deprecations object", func() { 2524 By("patching the OperatorGroup to reduce the bundle unpacking timeout") 2525 ogNN := types.NamespacedName{Name: operatorGroup.GetName(), Namespace: generatedNamespace.GetName()} 2526 addBundleUnpackTimeoutOGAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "5m") 2527 2528 By("updating the catalog with a fixed v0.2.0 bundle image marked deprecated") 2529 provider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.2.0-deprecations.yaml")) 2530 Expect(err).To(BeNil()) 2531 err = magicCatalog.UpdateCatalog(context.Background(), provider) 2532 Expect(err).To(BeNil()) 2533 2534 By("waiting for the subscription to have v0.2.0 installed with a Bundle Deprecated condition") 2535 sub, err := fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionHasCondition( 2536 operatorsv1alpha1.SubscriptionBundleDeprecated, 2537 corev1.ConditionTrue, 2538 "", 2539 "olm.bundle/example-operator.v0.2.0: bundle \"example-operator.v0.2.0\" has been deprecated. Please switch to a different one.")) 2540 Expect(err).Should(BeNil()) 2541 2542 By("checking for the deprecated conditions") 2543 By(`Operator is deprecated at all three levels in the catalog`) 2544 packageCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionPackageDeprecated) 2545 Expect(packageCondition.Status).To(Equal(corev1.ConditionTrue)) 2546 channelCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionChannelDeprecated) 2547 Expect(channelCondition.Status).To(Equal(corev1.ConditionTrue)) 2548 bundleCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleDeprecated) 2549 Expect(bundleCondition.Status).To(Equal(corev1.ConditionTrue)) 2550 2551 By("verifying that a roll-up condition is present containing all deprecation conditions") 2552 By(`Roll-up condition should be present and contain deprecation messages from all three levels`) 2553 rollUpCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionDeprecated) 2554 Expect(rollUpCondition.Status).To(Equal(corev1.ConditionTrue)) 2555 Expect(rollUpCondition.Message).To(ContainSubstring(packageCondition.Message)) 2556 Expect(rollUpCondition.Message).To(ContainSubstring(channelCondition.Message)) 2557 Expect(rollUpCondition.Message).To(ContainSubstring(bundleCondition.Message)) 2558 2559 By("updating the catalog with a fixed v0.3.0 bundle image no longer marked deprecated") 2560 provider, err = NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.3.0.yaml")) 2561 Expect(err).To(BeNil()) 2562 err = magicCatalog.UpdateCatalog(context.Background(), provider) 2563 Expect(err).To(BeNil()) 2564 2565 By("waiting for the subscription to have v0.3.0 installed with no deprecation message present") 2566 _, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionDoesNotHaveCondition(operatorsv1alpha1.SubscriptionDeprecated)) 2567 Expect(err).Should(BeNil()) 2568 }) 2569 2570 It("[FLAKE] should report only package and channel deprecation conditions when bundle is no longer deprecated", func() { 2571 By("patching the OperatorGroup to reduce the bundle unpacking timeout") 2572 ogNN := types.NamespacedName{Name: operatorGroup.GetName(), Namespace: generatedNamespace.GetName()} 2573 addBundleUnpackTimeoutOGAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "5m") 2574 2575 By("updating the catalog with a fixed v0.2.0 bundle image marked deprecated at all levels") 2576 provider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.2.0-deprecations.yaml")) 2577 Expect(err).To(BeNil()) 2578 err = magicCatalog.UpdateCatalog(context.Background(), provider) 2579 Expect(err).To(BeNil()) 2580 2581 By("waiting for the subscription to have v0.2.0 installed with a Bundle Deprecated condition") 2582 sub, err := fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionHasCondition( 2583 operatorsv1alpha1.SubscriptionBundleDeprecated, 2584 corev1.ConditionTrue, 2585 "", 2586 "olm.bundle/example-operator.v0.2.0: bundle \"example-operator.v0.2.0\" has been deprecated. Please switch to a different one.")) 2587 Expect(err).Should(BeNil()) 2588 2589 By("checking for the bundle deprecated condition") 2590 bundleCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleDeprecated) 2591 Expect(bundleCondition.Status).To(Equal(corev1.ConditionTrue)) 2592 bundleDeprecatedMessage := bundleCondition.Message 2593 2594 By("updating the catalog with a fixed v0.3.0 bundle marked partially deprecated") 2595 provider, err = NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.3.0-deprecations.yaml")) 2596 Expect(err).To(BeNil()) 2597 err = magicCatalog.UpdateCatalog(context.Background(), provider) 2598 Expect(err).To(BeNil()) 2599 2600 By("waiting for the subscription to switch to v0.3.0") 2601 sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionHasCurrentCSV("example-operator.v0.3.0")) 2602 Expect(err).Should(BeNil()) 2603 2604 By("waiting for the subscription to have be at latest known") 2605 sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionStateAtLatestChecker()) 2606 Expect(err).Should(BeNil()) 2607 2608 By("waiting for the install plan pending to go away") 2609 sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, 2610 subscriptionHasCondition( 2611 operatorsv1alpha1.SubscriptionInstallPlanPending, 2612 corev1.ConditionUnknown, 2613 "", 2614 "", 2615 ), 2616 ) 2617 Expect(err).Should(BeNil()) 2618 2619 By("waiting for the subscription to have v0.3.0 installed without a bundle deprecated condition") 2620 sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, 2621 subscriptionHasCondition( 2622 operatorsv1alpha1.SubscriptionBundleDeprecated, 2623 corev1.ConditionUnknown, 2624 "", 2625 "", 2626 ), 2627 ) 2628 Expect(err).Should(BeNil()) 2629 2630 By("checking for the deprecated conditions") 2631 By(`Operator is deprecated at only Package and Channel levels`) 2632 packageCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionPackageDeprecated) 2633 Expect(packageCondition.Status).To(Equal(corev1.ConditionTrue)) 2634 channelCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionChannelDeprecated) 2635 Expect(channelCondition.Status).To(Equal(corev1.ConditionTrue)) 2636 2637 By("verifying that a roll-up condition is present not containing bundle deprecation condition") 2638 By(`Roll-up condition should be present and contain deprecation messages from Package and Channel levels`) 2639 rollUpCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionDeprecated) 2640 Expect(rollUpCondition.Status).To(Equal(corev1.ConditionTrue)) 2641 Expect(rollUpCondition.Message).To(ContainSubstring(packageCondition.Message)) 2642 Expect(rollUpCondition.Message).To(ContainSubstring(channelCondition.Message)) 2643 Expect(rollUpCondition.Message).ToNot(ContainSubstring(bundleDeprecatedMessage)) 2644 }) 2645 2646 It("should report deprecated status when catalog is updated to deprecate an installed bundle", func() { 2647 By("patching the OperatorGroup to reduce the bundle unpacking timeout") 2648 ogNN := types.NamespacedName{Name: operatorGroup.GetName(), Namespace: generatedNamespace.GetName()} 2649 addBundleUnpackTimeoutOGAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "5m") 2650 2651 By("updating the catalog with a fixed v0.2.0 bundle not marked deprecated") 2652 provider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.2.0.yaml")) 2653 Expect(err).To(BeNil()) 2654 err = magicCatalog.UpdateCatalog(context.Background(), provider) 2655 Expect(err).To(BeNil()) 2656 2657 By("waiting for the subscription to have v0.2.0 installed") 2658 sub, err := fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionHasCurrentCSV("example-operator.v0.2.0")) 2659 Expect(err).Should(BeNil()) 2660 2661 By("the subscription should not be marked deprecated") 2662 rollupCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionDeprecated) 2663 Expect(rollupCondition.Status).To(Equal(corev1.ConditionUnknown)) 2664 2665 By("updating the catalog with a fixed v0.2.0 bundle image marked deprecated at all levels") 2666 provider, err = NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.2.0-deprecations.yaml")) 2667 Expect(err).To(BeNil()) 2668 err = magicCatalog.UpdateCatalog(context.Background(), provider) 2669 Expect(err).To(BeNil()) 2670 2671 By("checking for the bundle deprecated condition") 2672 Eventually(func() (bool, error) { 2673 sub, err := fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionHasCurrentCSV("example-operator.v0.2.0")) 2674 if err != nil { 2675 return false, err 2676 } 2677 cond := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionDeprecated) 2678 if cond.Status != corev1.ConditionTrue { 2679 return false, fmt.Errorf("%s condition not found", operatorsv1alpha1.SubscriptionDeprecated) 2680 } 2681 2682 return true, nil 2683 }, 2*time.Minute, time.Second*10).Should(BeTrue()) 2684 }) 2685 }) 2686 }) 2687 When("bundle unpack retries are enabled", func() { 2688 It("should retry failing unpack jobs", func() { 2689 if ok, err := inKind(c); ok && err == nil { 2690 Skip("This spec fails when run using KIND cluster. See https://github.com/operator-framework/operator-lifecycle-manager/issues/2420 for more details") 2691 } else if err != nil { 2692 Skip("Could not determine whether running in a kind cluster. Skipping.") 2693 } 2694 By("Ensuring a registry to host bundle images") 2695 local, err := Local(c) 2696 Expect(err).NotTo(HaveOccurred(), "cannot determine if test running locally or on CI: %s", err) 2697 2698 var registryURL string 2699 var copyImage func(dst, dstTag, src, srcTag string) error 2700 if local { 2701 registryURL, err = createDockerRegistry(c, generatedNamespace.GetName()) 2702 Expect(err).NotTo(HaveOccurred(), "error creating container registry: %s", err) 2703 defer deleteDockerRegistry(c, generatedNamespace.GetName()) 2704 2705 By(`ensure registry pod is ready before attempting port-forwarding`) 2706 _ = awaitPod(GinkgoT(), c, generatedNamespace.GetName(), registryName, podReady) 2707 2708 err = registryPortForward(generatedNamespace.GetName()) 2709 Expect(err).NotTo(HaveOccurred(), "port-forwarding local registry: %s", err) 2710 copyImage = func(dst, dstTag, src, srcTag string) error { 2711 if !strings.HasPrefix(src, "docker://") { 2712 src = fmt.Sprintf("docker://%s", src) 2713 } 2714 if !strings.HasPrefix(dst, "docker://") { 2715 dst = fmt.Sprintf("docker://%s", dst) 2716 } 2717 _, err := skopeoLocalCopy(dst, dstTag, src, srcTag) 2718 return err 2719 } 2720 } else { 2721 registryURL = fmt.Sprintf("%s/%s", openshiftregistryFQDN, generatedNamespace.GetName()) 2722 registryAuthSecretName, err := getRegistryAuthSecretName(c, generatedNamespace.GetName()) 2723 Expect(err).NotTo(HaveOccurred(), "error getting openshift registry authentication: %s", err) 2724 copyImage = func(dst, dstTag, src, srcTag string) error { 2725 if !strings.HasPrefix(src, "docker://") { 2726 src = fmt.Sprintf("docker://%s", src) 2727 } 2728 if !strings.HasPrefix(dst, "docker://") { 2729 dst = fmt.Sprintf("docker://%s", dst) 2730 } 2731 skopeoArgs := skopeoCopyCmd(dst, dstTag, src, srcTag, registryAuthSecretName) 2732 err = createSkopeoPod(c, skopeoArgs, generatedNamespace.GetName(), registryAuthSecretName) 2733 if err != nil { 2734 return fmt.Errorf("error creating skopeo pod: %v", err) 2735 } 2736 2737 By(`wait for skopeo pod to exit successfully`) 2738 awaitPod(GinkgoT(), c, generatedNamespace.GetName(), skopeo, func(pod *corev1.Pod) bool { 2739 ctx.Ctx().Logf("skopeo pod status: %s (waiting for: %s)", pod.Status.Phase, corev1.PodSucceeded) 2740 return pod.Status.Phase == corev1.PodSucceeded 2741 }) 2742 2743 if err := deleteSkopeoPod(c, generatedNamespace.GetName()); err != nil { 2744 return fmt.Errorf("error deleting skopeo pod: %s", err) 2745 } 2746 return nil 2747 } 2748 } 2749 2750 By(`The remote image to be copied onto the local registry`) 2751 srcImage := "quay.io/olmtest/example-operator-bundle:" 2752 srcTag := "0.1.0" 2753 2754 By(`on-cluster image ref`) 2755 bundleImage := registryURL + "/unpack-retry-bundle:" 2756 bundleTag := genName("x") 2757 2758 unpackRetryCatalog := fmt.Sprintf(` 2759 schema: olm.package 2760 name: unpack-retry-package 2761 defaultChannel: stable 2762 --- 2763 schema: olm.channel 2764 package: unpack-retry-package 2765 name: stable 2766 entries: 2767 - name: example-operator.v0.1.0 2768 --- 2769 schema: olm.bundle 2770 name: example-operator.v0.1.0 2771 package: unpack-retry-package 2772 image: %s%s 2773 properties: 2774 - type: olm.package 2775 value: 2776 packageName: unpack-retry-package 2777 version: 1.0.0 2778 `, bundleImage, bundleTag) 2779 2780 By("creating a catalog referencing a non-existent bundle image") 2781 unpackRetryProvider, err := NewRawFileBasedCatalogProvider(unpackRetryCatalog) 2782 Expect(err).ToNot(HaveOccurred()) 2783 catalogSourceName := fmt.Sprintf("%s-catsrc", generatedNamespace.GetName()) 2784 magicCatalog := NewMagicCatalog(ctx.Ctx().Client(), generatedNamespace.GetName(), catalogSourceName, unpackRetryProvider) 2785 Expect(magicCatalog.DeployCatalog(context.Background())).To(BeNil()) 2786 2787 By("patching the OperatorGroup to reduce the bundle unpacking timeout") 2788 ogNN := types.NamespacedName{Name: operatorGroup.GetName(), Namespace: generatedNamespace.GetName()} 2789 addBundleUnpackTimeoutOGAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "1s") 2790 2791 By("creating a subscription for the missing bundle") 2792 unpackRetrySubName := fmt.Sprintf("%s-unpack-retry-package-sub", generatedNamespace.GetName()) 2793 createSubscriptionForCatalog(crc, generatedNamespace.GetName(), unpackRetrySubName, catalogSourceName, "unpack-retry-package", stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) 2794 2795 By("waiting for bundle unpack to fail") 2796 Eventually( 2797 func() error { 2798 fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), unpackRetrySubName, metav1.GetOptions{}) 2799 if err != nil { 2800 return err 2801 } 2802 if cond := fetched.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleUnpackFailed); cond.Status != corev1.ConditionTrue || cond.Reason != "BundleUnpackFailed" { 2803 return fmt.Errorf("%s condition not found", operatorsv1alpha1.SubscriptionBundleUnpackFailed) 2804 } 2805 return nil 2806 }, 2807 5*time.Minute, 2808 interval, 2809 ).Should(Succeed()) 2810 2811 By("pushing missing bundle image") 2812 Expect(copyImage(bundleImage, bundleTag, srcImage, srcTag)).To(Succeed()) 2813 2814 By("patching the OperatorGroup to increase the bundle unpacking timeout") 2815 addBundleUnpackTimeoutOGAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "") // revert to default unpack timeout 2816 2817 By("patching operator group to enable unpack retries") 2818 setBundleUnpackRetryMinimumIntervalAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "1s") 2819 2820 By("waiting until the subscription has an IP reference") 2821 subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), unpackRetrySubName, subscriptionHasInstallPlanChecker()) 2822 Expect(err).Should(BeNil()) 2823 2824 By("waiting for the v0.1.0 CSV to report a succeeded phase") 2825 _, err = fetchCSV(crc, generatedNamespace.GetName(), subscription.Status.CurrentCSV, buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded)) 2826 Expect(err).ShouldNot(HaveOccurred()) 2827 2828 By("checking if old unpack conditions on subscription are removed") 2829 Eventually(func() error { 2830 fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), unpackRetrySubName, metav1.GetOptions{}) 2831 if err != nil { 2832 return err 2833 } 2834 if cond := fetched.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleUnpacking); cond.Status != corev1.ConditionUnknown { 2835 return fmt.Errorf("subscription condition %s has unexpected value %s, expected %s", operatorsv1alpha1.SubscriptionBundleUnpacking, cond.Status, corev1.ConditionFalse) 2836 } 2837 if cond := fetched.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleUnpackFailed); cond.Status != corev1.ConditionUnknown { 2838 return fmt.Errorf("unexpected condition %s on subscription", operatorsv1alpha1.SubscriptionBundleUnpackFailed) 2839 } 2840 return nil 2841 }).Should(Succeed()) 2842 }) 2843 2844 It("should not retry successful unpack jobs", func() { 2845 By("deploying the testing catalog") 2846 provider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.1.0.yaml")) 2847 Expect(err).To(BeNil()) 2848 catalogSourceName := fmt.Sprintf("%s-catsrc", generatedNamespace.GetName()) 2849 magicCatalog := NewMagicCatalog(ctx.Ctx().Client(), generatedNamespace.GetName(), catalogSourceName, provider) 2850 Expect(magicCatalog.DeployCatalog(context.Background())).To(BeNil()) 2851 2852 By("creating the testing subscription") 2853 subName := fmt.Sprintf("%s-test-package-sub", generatedNamespace.GetName()) 2854 createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subName, catalogSourceName, "test-package", stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) 2855 2856 By("waiting until the subscription has an IP reference") 2857 subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionHasInstallPlanChecker()) 2858 Expect(err).Should(BeNil()) 2859 2860 By("waiting for the v0.1.0 CSV to report a succeeded phase") 2861 _, err = fetchCSV(crc, generatedNamespace.GetName(), subscription.Status.CurrentCSV, buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded)) 2862 Expect(err).ShouldNot(HaveOccurred()) 2863 2864 By("waiting for the subscription bundle unpack conditions to be scrubbed") 2865 // This step removes flakes from this test where the conditions on the subscription haven't been 2866 // updated by the time the Consistently block executed a couple of steps below to ensure that the unpack 2867 // job has not been retried 2868 Eventually(func() error { 2869 fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), subName, metav1.GetOptions{}) 2870 if err != nil { 2871 return err 2872 } 2873 if cond := fetched.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleUnpacking); cond.Status == corev1.ConditionTrue { 2874 return fmt.Errorf("unexpected condition status for %s on subscription %s", operatorsv1alpha1.SubscriptionBundleUnpacking, subName) 2875 } 2876 if cond := fetched.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleUnpackFailed); cond.Status == corev1.ConditionTrue { 2877 return fmt.Errorf("unexpected condition status for %s on subscription %s", operatorsv1alpha1.SubscriptionBundleUnpackFailed, subName) 2878 } 2879 return nil 2880 }).Should(Succeed()) 2881 2882 By("patching operator group to enable unpack retries") 2883 ogNN := types.NamespacedName{Name: operatorGroup.GetName(), Namespace: generatedNamespace.GetName()} 2884 setBundleUnpackRetryMinimumIntervalAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "1s") 2885 2886 By("Ensuring successful bundle unpack jobs are not retried") 2887 Consistently(func() error { 2888 fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), subName, metav1.GetOptions{}) 2889 if err != nil { 2890 return err 2891 } 2892 if cond := fetched.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleUnpacking); cond.Status == corev1.ConditionTrue { 2893 return fmt.Errorf("unexpected condition status for %s on subscription %s", operatorsv1alpha1.SubscriptionBundleUnpacking, subName) 2894 } 2895 if cond := fetched.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleUnpackFailed); cond.Status == corev1.ConditionTrue { 2896 return fmt.Errorf("unexpected condition status for %s on subscription %s", operatorsv1alpha1.SubscriptionBundleUnpackFailed, subName) 2897 } 2898 return nil 2899 }).Should(Succeed()) 2900 }) 2901 }) 2902 }) 2903 2904 const ( 2905 catalogSourceName = "mock-ocs" 2906 catalogConfigMapName = "mock-ocs" 2907 testSubscriptionName = "mysubscription" 2908 testPackageName = "myapp" 2909 2910 stableChannel = "stable" 2911 betaChannel = "beta" 2912 alphaChannel = "alpha" 2913 2914 outdated = "myapp-outdated" 2915 stable = "myapp-stable" 2916 alpha = "myapp-alpha" 2917 beta = "myapp-beta" 2918 ) 2919 2920 var ( 2921 dummyManifest = []registry.PackageManifest{{ 2922 PackageName: testPackageName, 2923 Channels: []registry.PackageChannel{ 2924 {Name: stableChannel, CurrentCSVName: stable}, 2925 {Name: betaChannel, CurrentCSVName: beta}, 2926 {Name: alphaChannel, CurrentCSVName: alpha}, 2927 }, 2928 DefaultChannelName: stableChannel, 2929 }} 2930 csvType = metav1.TypeMeta{ 2931 Kind: operatorsv1alpha1.ClusterServiceVersionKind, 2932 APIVersion: operatorsv1alpha1.GroupVersion, 2933 } 2934 2935 strategy = operatorsv1alpha1.StrategyDetailsDeployment{ 2936 DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ 2937 { 2938 Name: genName("dep-"), 2939 Spec: newNginxDeployment(genName("nginx-")), 2940 }, 2941 }, 2942 } 2943 strategyRaw, _ = json.Marshal(strategy) 2944 installStrategy = operatorsv1alpha1.NamedInstallStrategy{ 2945 StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, 2946 StrategySpec: strategy, 2947 } 2948 outdatedCSV = operatorsv1alpha1.ClusterServiceVersion{ 2949 TypeMeta: csvType, 2950 ObjectMeta: metav1.ObjectMeta{ 2951 Name: outdated, 2952 }, 2953 Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ 2954 Replaces: "", 2955 Version: version.OperatorVersion{Version: semver.MustParse("0.1.0")}, 2956 MinKubeVersion: "0.0.0", 2957 InstallModes: []operatorsv1alpha1.InstallMode{ 2958 { 2959 Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, 2960 Supported: true, 2961 }, 2962 { 2963 Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, 2964 Supported: true, 2965 }, 2966 { 2967 Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, 2968 Supported: true, 2969 }, 2970 { 2971 Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, 2972 Supported: true, 2973 }, 2974 }, 2975 InstallStrategy: installStrategy, 2976 }, 2977 } 2978 stableCSV = operatorsv1alpha1.ClusterServiceVersion{ 2979 TypeMeta: csvType, 2980 ObjectMeta: metav1.ObjectMeta{ 2981 Name: stable, 2982 }, 2983 Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ 2984 Replaces: outdated, 2985 Version: version.OperatorVersion{Version: semver.MustParse("0.2.0")}, 2986 MinKubeVersion: "0.0.0", 2987 InstallModes: []operatorsv1alpha1.InstallMode{ 2988 { 2989 Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, 2990 Supported: true, 2991 }, 2992 { 2993 Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, 2994 Supported: true, 2995 }, 2996 { 2997 Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, 2998 Supported: true, 2999 }, 3000 { 3001 Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, 3002 Supported: true, 3003 }, 3004 }, 3005 InstallStrategy: installStrategy, 3006 }, 3007 } 3008 betaCSV = operatorsv1alpha1.ClusterServiceVersion{ 3009 TypeMeta: csvType, 3010 ObjectMeta: metav1.ObjectMeta{ 3011 Name: beta, 3012 }, 3013 Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ 3014 Replaces: stable, 3015 Version: version.OperatorVersion{Version: semver.MustParse("0.1.1")}, 3016 InstallModes: []operatorsv1alpha1.InstallMode{ 3017 { 3018 Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, 3019 Supported: true, 3020 }, 3021 { 3022 Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, 3023 Supported: true, 3024 }, 3025 { 3026 Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, 3027 Supported: true, 3028 }, 3029 { 3030 Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, 3031 Supported: true, 3032 }, 3033 }, 3034 InstallStrategy: installStrategy, 3035 }, 3036 } 3037 alphaCSV = operatorsv1alpha1.ClusterServiceVersion{ 3038 TypeMeta: csvType, 3039 ObjectMeta: metav1.ObjectMeta{ 3040 Name: alpha, 3041 }, 3042 Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ 3043 Replaces: beta, 3044 Version: version.OperatorVersion{Version: semver.MustParse("0.3.0")}, 3045 InstallModes: []operatorsv1alpha1.InstallMode{ 3046 { 3047 Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, 3048 Supported: true, 3049 }, 3050 { 3051 Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, 3052 Supported: true, 3053 }, 3054 { 3055 Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, 3056 Supported: true, 3057 }, 3058 { 3059 Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, 3060 Supported: true, 3061 }, 3062 }, 3063 InstallStrategy: installStrategy, 3064 }, 3065 } 3066 csvList = []operatorsv1alpha1.ClusterServiceVersion{outdatedCSV, stableCSV, betaCSV, alphaCSV} 3067 3068 strategyNew = operatorsv1alpha1.StrategyDetailsDeployment{ 3069 DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ 3070 { 3071 Name: genName("dep-"), 3072 Spec: appsv1.DeploymentSpec{ 3073 Selector: &metav1.LabelSelector{ 3074 MatchLabels: map[string]string{"app": "nginx"}, 3075 }, 3076 Replicas: &singleInstance, 3077 Template: corev1.PodTemplateSpec{ 3078 ObjectMeta: metav1.ObjectMeta{ 3079 Labels: map[string]string{"app": "nginx"}, 3080 }, 3081 Spec: corev1.PodSpec{Containers: []corev1.Container{ 3082 { 3083 Name: genName("nginx"), 3084 Image: *dummyImage, 3085 Ports: []corev1.ContainerPort{{ContainerPort: 80}}, 3086 ImagePullPolicy: corev1.PullIfNotPresent, 3087 }, 3088 }}, 3089 }, 3090 }, 3091 }, 3092 }, 3093 } 3094 3095 dummyCatalogConfigMap = &corev1.ConfigMap{ 3096 ObjectMeta: metav1.ObjectMeta{ 3097 Name: catalogConfigMapName, 3098 }, 3099 Data: map[string]string{}, 3100 } 3101 3102 dummyCatalogSource = operatorsv1alpha1.CatalogSource{ 3103 TypeMeta: metav1.TypeMeta{ 3104 Kind: operatorsv1alpha1.CatalogSourceKind, 3105 APIVersion: operatorsv1alpha1.CatalogSourceCRDAPIVersion, 3106 }, 3107 ObjectMeta: metav1.ObjectMeta{ 3108 Name: catalogSourceName, 3109 }, 3110 Spec: operatorsv1alpha1.CatalogSourceSpec{ 3111 SourceType: "internal", 3112 ConfigMap: catalogConfigMapName, 3113 GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ 3114 SecurityContextConfig: operatorsv1alpha1.Restricted, 3115 }, 3116 }, 3117 } 3118 ) 3119 3120 func init() { 3121 for i := 0; i < len(csvList); i++ { 3122 csvList[i].Spec.InstallStrategy.StrategySpec = strategyNew 3123 } 3124 3125 manifestsRaw, err := yaml.Marshal(dummyManifest) 3126 if err != nil { 3127 panic(err) 3128 } 3129 dummyCatalogConfigMap.Data[registry.ConfigMapPackageName] = string(manifestsRaw) 3130 csvsRaw, err := yaml.Marshal(csvList) 3131 if err != nil { 3132 panic(err) 3133 } 3134 dummyCatalogConfigMap.Data[registry.ConfigMapCSVName] = string(csvsRaw) 3135 dummyCatalogConfigMap.Data[registry.ConfigMapCRDName] = "" 3136 } 3137 3138 func initCatalog(t GinkgoTInterface, namespace string, c operatorclient.ClientInterface, crc versioned.Interface) error { 3139 dummyCatalogConfigMap.SetNamespace(namespace) 3140 if _, err := c.KubernetesInterface().CoreV1().ConfigMaps(namespace).Create(context.Background(), dummyCatalogConfigMap, metav1.CreateOptions{}); err != nil { 3141 if apierrors.IsAlreadyExists(err) { 3142 return fmt.Errorf("E2E bug detected: %v", err) 3143 } 3144 return err 3145 } 3146 t.Logf("created configmap %s/%s", dummyCatalogConfigMap.Namespace, dummyCatalogConfigMap.Name) 3147 3148 dummyCatalogSource.SetNamespace(namespace) 3149 if _, err := crc.OperatorsV1alpha1().CatalogSources(namespace).Create(context.Background(), &dummyCatalogSource, metav1.CreateOptions{}); err != nil { 3150 if apierrors.IsAlreadyExists(err) { 3151 return fmt.Errorf("E2E bug detected: %v", err) 3152 } 3153 return err 3154 } 3155 t.Logf("created catalog source %s/%s", dummyCatalogSource.Namespace, dummyCatalogSource.Name) 3156 3157 fetched, err := fetchCatalogSourceOnStatus(crc, dummyCatalogSource.GetName(), dummyCatalogSource.GetNamespace(), catalogSourceRegistryPodSynced()) 3158 require.NoError(t, err) 3159 require.NotNil(t, fetched) 3160 3161 return nil 3162 } 3163 3164 type subscriptionStateChecker func(subscription *operatorsv1alpha1.Subscription) bool 3165 3166 func subscriptionStateUpgradeAvailableChecker() func(subscription *operatorsv1alpha1.Subscription) bool { 3167 var lastState operatorsv1alpha1.SubscriptionState 3168 lastTime := time.Now() 3169 return func(subscription *operatorsv1alpha1.Subscription) bool { 3170 if subscription.Status.State != lastState { 3171 ctx.Ctx().Logf("waiting %s for subscription %s/%s to have state %s: has state %s", time.Since(lastTime), subscription.Namespace, subscription.Name, operatorsv1alpha1.SubscriptionStateUpgradeAvailable, subscription.Status.State) 3172 lastState = subscription.Status.State 3173 lastTime = time.Now() 3174 } 3175 return subscription.Status.State == operatorsv1alpha1.SubscriptionStateUpgradeAvailable 3176 } 3177 } 3178 3179 func subscriptionStateUpgradePendingChecker() func(subscription *operatorsv1alpha1.Subscription) bool { 3180 var lastState operatorsv1alpha1.SubscriptionState 3181 lastTime := time.Now() 3182 return func(subscription *operatorsv1alpha1.Subscription) bool { 3183 if subscription.Status.State != lastState { 3184 ctx.Ctx().Logf("waiting %s for subscription %s/%s to have state %s: has state %s", time.Since(lastTime), subscription.Namespace, subscription.Name, operatorsv1alpha1.SubscriptionStateUpgradePending, subscription.Status.State) 3185 lastState = subscription.Status.State 3186 lastTime = time.Now() 3187 } 3188 return subscription.Status.State == operatorsv1alpha1.SubscriptionStateUpgradePending 3189 } 3190 } 3191 3192 func subscriptionStateAtLatestChecker() func(subscription *operatorsv1alpha1.Subscription) bool { 3193 var lastState operatorsv1alpha1.SubscriptionState 3194 lastTime := time.Now() 3195 return func(subscription *operatorsv1alpha1.Subscription) bool { 3196 if subscription.Status.State != lastState { 3197 ctx.Ctx().Logf("waiting %s for subscription %s/%s to have state %s: has state %s", time.Since(lastTime), subscription.Namespace, subscription.Name, operatorsv1alpha1.SubscriptionStateAtLatest, subscription.Status.State) 3198 lastState = subscription.Status.State 3199 lastTime = time.Now() 3200 } 3201 return subscription.Status.State == operatorsv1alpha1.SubscriptionStateAtLatest 3202 } 3203 } 3204 3205 func subscriptionHasInstallPlanChecker() func(subscription *operatorsv1alpha1.Subscription) bool { 3206 var lastState operatorsv1alpha1.SubscriptionState 3207 lastTime := time.Now() 3208 return func(subscription *operatorsv1alpha1.Subscription) bool { 3209 if subscription.Status.State != lastState { 3210 ctx.Ctx().Logf("waiting %s for subscription %s/%s to have installplan ref: has ref %#v", time.Since(lastTime), subscription.Namespace, subscription.Name, subscription.Status.InstallPlanRef) 3211 lastState = subscription.Status.State 3212 lastTime = time.Now() 3213 } 3214 return subscription.Status.InstallPlanRef != nil 3215 } 3216 } 3217 3218 func subscriptionHasInstallPlanDifferentChecker(currentInstallPlanName string) subscriptionStateChecker { 3219 checker := subscriptionHasInstallPlanChecker() 3220 var lastState operatorsv1alpha1.SubscriptionState 3221 lastTime := time.Now() 3222 return func(subscription *operatorsv1alpha1.Subscription) bool { 3223 if subscription.Status.State != lastState { 3224 ctx.Ctx().Logf("waiting %s for subscription %s/%s to have installplan different from %s: has ref %#v", time.Since(lastTime), subscription.Namespace, subscription.Name, currentInstallPlanName, subscription.Status.InstallPlanRef) 3225 lastState = subscription.Status.State 3226 lastTime = time.Now() 3227 } 3228 return checker(subscription) && subscription.Status.InstallPlanRef.Name != currentInstallPlanName 3229 } 3230 } 3231 3232 func subscriptionHasCurrentCSV(currentCSV string) subscriptionStateChecker { 3233 return func(subscription *operatorsv1alpha1.Subscription) bool { 3234 return subscription.Status.CurrentCSV == currentCSV 3235 } 3236 } 3237 3238 func subscriptionHasCondition(condType operatorsv1alpha1.SubscriptionConditionType, status corev1.ConditionStatus, reason, message string) subscriptionStateChecker { 3239 var lastCond operatorsv1alpha1.SubscriptionCondition 3240 lastTime := time.Now() 3241 // if status/reason/message meet expectations, then subscription state is considered met/true 3242 // IFF this is the result of a recent change of status/reason/message 3243 // else, cache the current status/reason/message for next loop/comparison 3244 return func(subscription *operatorsv1alpha1.Subscription) bool { 3245 cond := subscription.Status.GetCondition(condType) 3246 if cond.Status == status && cond.Reason == reason && cond.Message == message { 3247 if lastCond.Status != cond.Status && lastCond.Reason != cond.Reason && lastCond.Message == cond.Message { 3248 GinkgoT().Logf("waited %s subscription condition met %v\n", time.Since(lastTime), cond) 3249 lastTime = time.Now() 3250 lastCond = cond 3251 } 3252 return true 3253 } 3254 3255 if lastCond.Status != cond.Status && lastCond.Reason != cond.Reason && lastCond.Message == cond.Message { 3256 GinkgoT().Logf("waited %s subscription condition not met: %v\n", time.Since(lastTime), cond) 3257 lastTime = time.Now() 3258 lastCond = cond 3259 } 3260 return false 3261 } 3262 } 3263 3264 func subscriptionDoesNotHaveCondition(condType operatorsv1alpha1.SubscriptionConditionType) subscriptionStateChecker { 3265 var lastStatus corev1.ConditionStatus 3266 lastTime := time.Now() 3267 // if status meets expectations, then subscription state is considered met/true 3268 // IFF this is the result of a recent change of status 3269 // else, cache the current status for next loop/comparison 3270 return func(subscription *operatorsv1alpha1.Subscription) bool { 3271 cond := subscription.Status.GetCondition(condType) 3272 if cond.Status == corev1.ConditionUnknown { 3273 if cond.Status != lastStatus { 3274 GinkgoT().Logf("waited %s subscription condition not found\n", time.Since(lastTime)) 3275 lastStatus = cond.Status 3276 lastTime = time.Now() 3277 } 3278 return true 3279 } 3280 3281 if cond.Status != lastStatus { 3282 GinkgoT().Logf("waited %s subscription condition found: %v\n", time.Since(lastTime), cond) 3283 lastStatus = cond.Status 3284 lastTime = time.Now() 3285 } 3286 return false 3287 } 3288 } 3289 3290 func fetchSubscription(crc versioned.Interface, namespace, name string, checker subscriptionStateChecker) (*operatorsv1alpha1.Subscription, error) { 3291 var fetchedSubscription *operatorsv1alpha1.Subscription 3292 3293 log := func(s string) { 3294 ctx.Ctx().Logf("%s: %s", time.Now().Format("15:04:05.9999"), s) 3295 } 3296 3297 var lastState operatorsv1alpha1.SubscriptionState 3298 var lastCSV string 3299 var lastInstallPlanRef *corev1.ObjectReference 3300 3301 err := wait.Poll(pollInterval, pollDuration, func() (bool, error) { 3302 var err error 3303 fetchedSubscription, err = crc.OperatorsV1alpha1().Subscriptions(namespace).Get(context.Background(), name, metav1.GetOptions{}) 3304 if err != nil || fetchedSubscription == nil { 3305 log(fmt.Sprintf("error getting subscription %s/%s: %v", namespace, name, err)) 3306 return false, nil 3307 } 3308 thisState, thisCSV, thisInstallPlanRef := fetchedSubscription.Status.State, fetchedSubscription.Status.CurrentCSV, fetchedSubscription.Status.InstallPlanRef 3309 if thisState != lastState || thisCSV != lastCSV || !equality.Semantic.DeepEqual(thisInstallPlanRef, lastInstallPlanRef) { 3310 lastState, lastCSV, lastInstallPlanRef = thisState, thisCSV, thisInstallPlanRef 3311 log(fmt.Sprintf("subscription %s/%s state: %s (csv %s): installPlanRef: %#v", namespace, name, thisState, thisCSV, thisInstallPlanRef)) 3312 log(fmt.Sprintf("subscription %s/%s state: %s (csv %s): status: %#v", namespace, name, thisState, thisCSV, fetchedSubscription.Status)) 3313 } 3314 return checker(fetchedSubscription), nil 3315 }) 3316 if err != nil { 3317 log(fmt.Sprintf("subscription %s/%s never got correct status: %#v", namespace, name, fetchedSubscription.Status)) 3318 log(fmt.Sprintf("subscription %s/%s spec: %#v", namespace, name, fetchedSubscription.Spec)) 3319 return nil, err 3320 } 3321 return fetchedSubscription, nil 3322 } 3323 3324 func buildSubscriptionCleanupFunc(crc versioned.Interface, subscription *operatorsv1alpha1.Subscription) cleanupFunc { 3325 return func() { 3326 if env := os.Getenv("SKIP_CLEANUP"); env != "" { 3327 fmt.Printf("Skipping cleanup of install plan for subscription %s/%s...\n", subscription.GetNamespace(), subscription.GetName()) 3328 return 3329 } 3330 3331 if installPlanRef := subscription.Status.InstallPlanRef; installPlanRef != nil { 3332 installPlan, err := crc.OperatorsV1alpha1().InstallPlans(subscription.GetNamespace()).Get(context.Background(), installPlanRef.Name, metav1.GetOptions{}) 3333 if err == nil { 3334 buildInstallPlanCleanupFunc(crc, subscription.GetNamespace(), installPlan)() 3335 } else { 3336 ctx.Ctx().Logf("Could not get installplan %s while building subscription %s's cleanup function", installPlan.GetName(), subscription.GetName()) 3337 } 3338 } 3339 3340 err := crc.OperatorsV1alpha1().Subscriptions(subscription.GetNamespace()).Delete(context.Background(), subscription.GetName(), metav1.DeleteOptions{}) 3341 Expect(err).NotTo(HaveOccurred()) 3342 } 3343 } 3344 3345 func createSubscription(t GinkgoTInterface, crc versioned.Interface, namespace, name, packageName, channel string, approval operatorsv1alpha1.Approval) (cleanupFunc, *operatorsv1alpha1.Subscription) { 3346 subscription := &operatorsv1alpha1.Subscription{ 3347 TypeMeta: metav1.TypeMeta{ 3348 Kind: operatorsv1alpha1.SubscriptionKind, 3349 APIVersion: operatorsv1alpha1.SubscriptionCRDAPIVersion, 3350 }, 3351 ObjectMeta: metav1.ObjectMeta{ 3352 Namespace: namespace, 3353 Name: name, 3354 }, 3355 Spec: &operatorsv1alpha1.SubscriptionSpec{ 3356 CatalogSource: catalogSourceName, 3357 CatalogSourceNamespace: namespace, 3358 Package: packageName, 3359 Channel: channel, 3360 InstallPlanApproval: approval, 3361 }, 3362 } 3363 3364 subscription, err := crc.OperatorsV1alpha1().Subscriptions(namespace).Create(context.Background(), subscription, metav1.CreateOptions{}) 3365 Expect(err).ToNot(HaveOccurred()) 3366 t.Logf("created subscription %s/%s", subscription.Namespace, subscription.Name) 3367 return buildSubscriptionCleanupFunc(crc, subscription), subscription 3368 } 3369 3370 func createSubscriptionForCatalog(crc versioned.Interface, namespace, name, catalog, packageName, channel, startingCSV string, approval operatorsv1alpha1.Approval) cleanupFunc { 3371 subscription := &operatorsv1alpha1.Subscription{ 3372 TypeMeta: metav1.TypeMeta{ 3373 Kind: operatorsv1alpha1.SubscriptionKind, 3374 APIVersion: operatorsv1alpha1.SubscriptionCRDAPIVersion, 3375 }, 3376 ObjectMeta: metav1.ObjectMeta{ 3377 Namespace: namespace, 3378 Name: name, 3379 }, 3380 Spec: &operatorsv1alpha1.SubscriptionSpec{ 3381 CatalogSource: catalog, 3382 CatalogSourceNamespace: namespace, 3383 Package: packageName, 3384 Channel: channel, 3385 StartingCSV: startingCSV, 3386 InstallPlanApproval: approval, 3387 }, 3388 } 3389 3390 subscription, err := crc.OperatorsV1alpha1().Subscriptions(namespace).Create(context.Background(), subscription, metav1.CreateOptions{}) 3391 Expect(err).NotTo(HaveOccurred()) 3392 return buildSubscriptionCleanupFunc(crc, subscription) 3393 } 3394 3395 func createSubscriptionForCatalogWithSpec(t GinkgoTInterface, crc versioned.Interface, namespace, name string, spec *operatorsv1alpha1.SubscriptionSpec) cleanupFunc { 3396 subscription := &operatorsv1alpha1.Subscription{ 3397 TypeMeta: metav1.TypeMeta{ 3398 Kind: operatorsv1alpha1.SubscriptionKind, 3399 APIVersion: operatorsv1alpha1.SubscriptionCRDAPIVersion, 3400 }, 3401 ObjectMeta: metav1.ObjectMeta{ 3402 Namespace: namespace, 3403 Name: name, 3404 }, 3405 Spec: spec, 3406 } 3407 3408 subscription, err := crc.OperatorsV1alpha1().Subscriptions(namespace).Create(context.Background(), subscription, metav1.CreateOptions{}) 3409 require.NoError(t, err) 3410 return buildSubscriptionCleanupFunc(crc, subscription) 3411 } 3412 3413 func waitForSubscriptionToDelete(namespace, name string, c versioned.Interface) error { 3414 var lastState operatorsv1alpha1.SubscriptionState 3415 var lastReason operatorsv1alpha1.ConditionReason 3416 lastTime := time.Now() 3417 3418 ctx.Ctx().Logf("waiting for subscription %s/%s to delete", namespace, name) 3419 err := wait.Poll(pollInterval, pollDuration, func() (bool, error) { 3420 sub, err := c.OperatorsV1alpha1().Subscriptions(namespace).Get(context.TODO(), name, metav1.GetOptions{}) 3421 if apierrors.IsNotFound(err) { 3422 ctx.Ctx().Logf("subscription %s/%s deleted", namespace, name) 3423 return true, nil 3424 } 3425 if err != nil { 3426 ctx.Ctx().Logf("error getting subscription %s/%s: %v", namespace, name, err) 3427 } 3428 if sub != nil { 3429 state, reason := sub.Status.State, sub.Status.Reason 3430 if state != lastState || reason != lastReason { 3431 ctx.Ctx().Logf("waited %s for subscription %s/%s status: %s (%s)", time.Since(lastTime), namespace, name, state, reason) 3432 lastState, lastReason = state, reason 3433 lastTime = time.Now() 3434 } 3435 } 3436 return false, nil 3437 }) 3438 3439 return err 3440 } 3441 3442 func checkDeploymentHasPodConfigNodeSelector(t GinkgoTInterface, client operatorclient.ClientInterface, csv *operatorsv1alpha1.ClusterServiceVersion, nodeSelector map[string]string) error { 3443 3444 resolver := install.StrategyResolver{} 3445 3446 strategy, err := resolver.UnmarshalStrategy(csv.Spec.InstallStrategy) 3447 if err != nil { 3448 return err 3449 } 3450 3451 strategyDetailsDeployment, ok := strategy.(*operatorsv1alpha1.StrategyDetailsDeployment) 3452 require.Truef(t, ok, "could not cast install strategy as type %T", strategyDetailsDeployment) 3453 3454 for _, deploymentSpec := range strategyDetailsDeployment.DeploymentSpecs { 3455 deployment, err := client.KubernetesInterface().AppsV1().Deployments(csv.GetNamespace()).Get(context.Background(), deploymentSpec.Name, metav1.GetOptions{}) 3456 if err != nil { 3457 return err 3458 } 3459 3460 isEqual := reflect.DeepEqual(nodeSelector, deployment.Spec.Template.Spec.NodeSelector) 3461 if !isEqual { 3462 err = fmt.Errorf("actual nodeSelector=%v does not match expected nodeSelector=%v", deploymentSpec.Spec.Template.Spec.NodeSelector, nodeSelector) 3463 } 3464 } 3465 return nil 3466 } 3467 3468 func checkDeploymentWithPodConfiguration(client operatorclient.ClientInterface, csv *operatorsv1alpha1.ClusterServiceVersion, envVar []corev1.EnvVar, volumes []corev1.Volume, volumeMounts []corev1.VolumeMount, tolerations []corev1.Toleration, resources *corev1.ResourceRequirements) error { 3469 resolver := install.StrategyResolver{} 3470 3471 strategy, err := resolver.UnmarshalStrategy(csv.Spec.InstallStrategy) 3472 Expect(err).NotTo(HaveOccurred()) 3473 3474 strategyDetailsDeployment, ok := strategy.(*operatorsv1alpha1.StrategyDetailsDeployment) 3475 Expect(ok).To(BeTrue(), "could not cast install strategy as type %T", strategyDetailsDeployment) 3476 3477 findEnvVar := func(envVar []corev1.EnvVar, name string) (foundEnvVar *corev1.EnvVar, found bool) { 3478 for i := range envVar { 3479 if name == envVar[i].Name { 3480 found = true 3481 foundEnvVar = &envVar[i] 3482 3483 break 3484 } 3485 } 3486 3487 return 3488 } 3489 3490 findVolumeMount := func(volumeMounts []corev1.VolumeMount, name string) (foundVolumeMount *corev1.VolumeMount, found bool) { 3491 for i := range volumeMounts { 3492 if name == volumeMounts[i].Name { 3493 found = true 3494 foundVolumeMount = &volumeMounts[i] 3495 3496 break 3497 } 3498 } 3499 3500 return 3501 } 3502 3503 findVolume := func(volumes []corev1.Volume, name string) (foundVolume *corev1.Volume, found bool) { 3504 for i := range volumes { 3505 if name == volumes[i].Name { 3506 found = true 3507 foundVolume = &volumes[i] 3508 3509 break 3510 } 3511 } 3512 3513 return 3514 } 3515 3516 findTolerations := func(tolerations []corev1.Toleration, toleration corev1.Toleration) (foundToleration *corev1.Toleration, found bool) { 3517 for i := range tolerations { 3518 if reflect.DeepEqual(toleration, tolerations[i]) { 3519 found = true 3520 foundToleration = &toleration 3521 3522 break 3523 } 3524 } 3525 3526 return 3527 } 3528 3529 findResources := func(existingResource *corev1.ResourceRequirements, podResource *corev1.ResourceRequirements) (foundResource *corev1.ResourceRequirements, found bool) { 3530 if reflect.DeepEqual(existingResource, podResource) { 3531 found = true 3532 foundResource = podResource 3533 } 3534 3535 return 3536 } 3537 3538 check := func(container *corev1.Container) error { 3539 for _, e := range envVar { 3540 existing, found := findEnvVar(container.Env, e.Name) 3541 if !found || existing == nil { 3542 return fmt.Errorf("env variable name=%s not injected", e.Name) 3543 } 3544 Expect(e.Value).Should(Equal(existing.Value), "env variable value does not match %s=%s", e.Name, e.Value) 3545 } 3546 3547 for _, v := range volumeMounts { 3548 existing, found := findVolumeMount(container.VolumeMounts, v.Name) 3549 if !found || existing == nil { 3550 return fmt.Errorf("VolumeMount name=%s not injected", v.Name) 3551 } 3552 Expect(v.MountPath).Should(Equal(existing.MountPath), "VolumeMount MountPath does not match %s=%s", v.Name, v.MountPath) 3553 } 3554 3555 existing, found := findResources(&container.Resources, resources) 3556 if !found || existing == nil { 3557 return fmt.Errorf("Resources not injected. Resource=%v", resources) 3558 } 3559 Expect(existing).Should(Equal(resources), "Resource=%v does not match expected Resource=%v", existing, resources) 3560 return nil 3561 } 3562 3563 for _, deploymentSpec := range strategyDetailsDeployment.DeploymentSpecs { 3564 deployment, err := client.KubernetesInterface().AppsV1().Deployments(csv.GetNamespace()).Get(context.Background(), deploymentSpec.Name, metav1.GetOptions{}) 3565 Expect(err).NotTo(HaveOccurred()) 3566 for _, v := range volumes { 3567 existing, found := findVolume(deployment.Spec.Template.Spec.Volumes, v.Name) 3568 if !found || existing == nil { 3569 return fmt.Errorf("Volume name=%s not injected", v.Name) 3570 } 3571 Expect(v.ConfigMap.LocalObjectReference.Name).Should(Equal(existing.ConfigMap.LocalObjectReference.Name), "volume ConfigMap Names does not match %s=%s", v.Name, v.ConfigMap.LocalObjectReference.Name) 3572 } 3573 3574 for _, toleration := range tolerations { 3575 existing, found := findTolerations(deployment.Spec.Template.Spec.Tolerations, toleration) 3576 if !found || existing == nil { 3577 return fmt.Errorf("Toleration not injected. Toleration=%v", toleration) 3578 } 3579 Expect(*existing).Should(Equal(toleration), "Toleration=%v does not match expected Toleration=%v", existing, toleration) 3580 } 3581 3582 for i := range deployment.Spec.Template.Spec.Containers { 3583 err = check(&deployment.Spec.Template.Spec.Containers[i]) 3584 if err != nil { 3585 return err 3586 } 3587 } 3588 } 3589 return nil 3590 } 3591 3592 func updateInternalCatalog(t GinkgoTInterface, c operatorclient.ClientInterface, crc versioned.Interface, catalogSourceName, namespace string, crds []apiextensionsv1.CustomResourceDefinition, csvs []operatorsv1alpha1.ClusterServiceVersion, packages []registry.PackageManifest) { 3593 fetchedInitialCatalog, err := fetchCatalogSourceOnStatus(crc, catalogSourceName, namespace, catalogSourceRegistryPodSynced()) 3594 require.NoError(t, err) 3595 3596 // Get initial configmap 3597 configMap, err := c.KubernetesInterface().CoreV1().ConfigMaps(namespace).Get(context.Background(), fetchedInitialCatalog.Spec.ConfigMap, metav1.GetOptions{}) 3598 require.NoError(t, err) 3599 3600 // Update package to point to new csv 3601 manifestsRaw, err := yaml.Marshal(packages) 3602 require.NoError(t, err) 3603 configMap.Data[registry.ConfigMapPackageName] = string(manifestsRaw) 3604 3605 // Update raw CRDs 3606 var crdsRaw []byte 3607 crdStrings := []string{} 3608 for _, crd := range crds { 3609 crdStrings = append(crdStrings, serializeCRD(crd)) 3610 } 3611 crdsRaw, err = yaml.Marshal(crdStrings) 3612 require.NoError(t, err) 3613 configMap.Data[registry.ConfigMapCRDName] = strings.Replace(string(crdsRaw), "- |\n ", "- ", -1) 3614 3615 // Update raw CSVs 3616 csvsRaw, err := yaml.Marshal(csvs) 3617 require.NoError(t, err) 3618 configMap.Data[registry.ConfigMapCSVName] = string(csvsRaw) 3619 3620 // Update configmap 3621 _, err = c.KubernetesInterface().CoreV1().ConfigMaps(namespace).Update(context.Background(), configMap, metav1.UpdateOptions{}) 3622 require.NoError(t, err) 3623 3624 // wait for catalog to update 3625 var lastState string 3626 lastTime := time.Now() 3627 _, err = fetchCatalogSourceOnStatus(crc, catalogSourceName, namespace, func(catalog *operatorsv1alpha1.CatalogSource) bool { 3628 before := fetchedInitialCatalog.Status.ConfigMapResource 3629 after := catalog.Status.ConfigMapResource 3630 if after != nil && after.LastUpdateTime.After(before.LastUpdateTime.Time) && after.ResourceVersion != before.ResourceVersion && 3631 catalog.Status.GRPCConnectionState.LastConnectTime.After(after.LastUpdateTime.Time) && catalog.Status.GRPCConnectionState.LastObservedState == "READY" { 3632 fmt.Println("catalog updated") 3633 return true 3634 } 3635 if catalog.Status.GRPCConnectionState.LastObservedState != lastState { 3636 fmt.Printf("waited %s for catalog pod %v to be available (after catalog update) - %s\n", time.Since(lastTime), catalog.GetName(), catalog.Status.GRPCConnectionState.LastObservedState) 3637 lastState = catalog.Status.GRPCConnectionState.LastObservedState 3638 lastTime = time.Now() 3639 } 3640 return false 3641 }) 3642 require.NoError(t, err) 3643 } 3644 3645 func subscriptionCurrentCSVGetter(crclient versioned.Interface, namespace, subName string) func() string { 3646 return func() string { 3647 subscription, err := crclient.OperatorsV1alpha1().Subscriptions(namespace).Get(context.Background(), subName, metav1.GetOptions{}) 3648 if err != nil || subscription == nil { 3649 return "" 3650 } 3651 return subscription.Status.CurrentCSV 3652 } 3653 }