From 8505836475e8060978198ff41cf7a9ff97de5cc6 Mon Sep 17 00:00:00 2001 From: Rashmi Gottipati Date: Sun, 18 May 2025 16:47:07 +0530 Subject: [PATCH 1/6] Upstream: 1675: Add NetworkPolicy as a supported kind Signed-off-by: Anik Bhattacharjee Upstream-repository: operator-registry Upstream-commit: 919aefdce3f4d6b77c3872df249815d20749db94 --- staging/operator-registry/pkg/lib/bundle/supported_resources.go | 2 ++ .../operator-registry/pkg/lib/bundle/supported_resources.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/staging/operator-registry/pkg/lib/bundle/supported_resources.go b/staging/operator-registry/pkg/lib/bundle/supported_resources.go index 94b5fd01df..a07b28aef3 100644 --- a/staging/operator-registry/pkg/lib/bundle/supported_resources.go +++ b/staging/operator-registry/pkg/lib/bundle/supported_resources.go @@ -20,6 +20,7 @@ const ( ConsoleQuickStartKind = "ConsoleQuickStart" ConsoleCLIDownloadKind = "ConsoleCLIDownload" ConsoleLinkKind = "ConsoleLink" + NetworkPolicyKind = "NetworkPolicy" ) // Namespaced indicates whether the resource is namespace scoped (true) or cluster-scoped (false). @@ -47,6 +48,7 @@ var supportedResources = map[string]Namespaced{ ConsoleQuickStartKind: false, ConsoleCLIDownloadKind: false, ConsoleLinkKind: false, + NetworkPolicyKind: true, } // IsSupported checks if the object kind is OLM-supported and if it is namespaced diff --git a/vendor/github.com/operator-framework/operator-registry/pkg/lib/bundle/supported_resources.go b/vendor/github.com/operator-framework/operator-registry/pkg/lib/bundle/supported_resources.go index 94b5fd01df..a07b28aef3 100644 --- a/vendor/github.com/operator-framework/operator-registry/pkg/lib/bundle/supported_resources.go +++ b/vendor/github.com/operator-framework/operator-registry/pkg/lib/bundle/supported_resources.go @@ -20,6 +20,7 @@ const ( ConsoleQuickStartKind = "ConsoleQuickStart" ConsoleCLIDownloadKind = "ConsoleCLIDownload" ConsoleLinkKind = "ConsoleLink" + NetworkPolicyKind = "NetworkPolicy" ) // Namespaced indicates whether the resource is namespace scoped (true) or cluster-scoped (false). @@ -47,6 +48,7 @@ var supportedResources = map[string]Namespaced{ ConsoleQuickStartKind: false, ConsoleCLIDownloadKind: false, ConsoleLinkKind: false, + NetworkPolicyKind: true, } // IsSupported checks if the object kind is OLM-supported and if it is namespaced From 1b90107ac3b0369c63c0094d0a2cf80f91044440 Mon Sep 17 00:00:00 2001 From: Rashmi Gottipati Date: Sun, 18 May 2025 16:53:44 +0530 Subject: [PATCH 2/6] Upstream: 3580: add NetworkPolicy as a supported kind Signed-off-by: Rashmi Gottipati Upstream-repository: operator-lifecycle-manager Upstream-commit: d7aaeb11ab80f862fc9c70dfb7865158bb67e28c --- .../pkg/controller/operators/catalog/supportedresources.go | 2 ++ .../pkg/controller/operators/catalog/supportedresources.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/staging/operator-lifecycle-manager/pkg/controller/operators/catalog/supportedresources.go b/staging/operator-lifecycle-manager/pkg/controller/operators/catalog/supportedresources.go index 4d0a97d1b6..8efc0f51da 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/operators/catalog/supportedresources.go +++ b/staging/operator-lifecycle-manager/pkg/controller/operators/catalog/supportedresources.go @@ -10,6 +10,7 @@ const ( ConsoleQuickStartKind = "ConsoleQuickStart" ConsoleCLIDownloadKind = "ConsoleCLIDownload" ConsoleLinkKind = "ConsoleLink" + NetworkPolicyKind = "NetworkPolicy" ) var supportedKinds = map[string]struct{}{ @@ -22,6 +23,7 @@ var supportedKinds = map[string]struct{}{ ConsoleQuickStartKind: {}, ConsoleCLIDownloadKind: {}, ConsoleLinkKind: {}, + NetworkPolicyKind: {}, } // isSupported returns true if OLM supports this type of CustomResource. diff --git a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/catalog/supportedresources.go b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/catalog/supportedresources.go index 4d0a97d1b6..8efc0f51da 100644 --- a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/catalog/supportedresources.go +++ b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/catalog/supportedresources.go @@ -10,6 +10,7 @@ const ( ConsoleQuickStartKind = "ConsoleQuickStart" ConsoleCLIDownloadKind = "ConsoleCLIDownload" ConsoleLinkKind = "ConsoleLink" + NetworkPolicyKind = "NetworkPolicy" ) var supportedKinds = map[string]struct{}{ @@ -22,6 +23,7 @@ var supportedKinds = map[string]struct{}{ ConsoleQuickStartKind: {}, ConsoleCLIDownloadKind: {}, ConsoleLinkKind: {}, + NetworkPolicyKind: {}, } // isSupported returns true if OLM supports this type of CustomResource. From 532583c0ade70bd003e8c98535f749daa36b042f Mon Sep 17 00:00:00 2001 From: Anik Bhattacharjee Date: Thu, 14 Aug 2025 13:53:32 -0400 Subject: [PATCH 3/6] Upstream: : Add allow-all networkpolicy for openshift-operators namespace --- manifests/0000_50_olm_01-networkpolicies.yaml | 19 +++++++++++++++++++ .../0000_50_olm_01-networkpolicies.yaml | 19 +++++++++++++++++++ microshift-manifests/kustomization.yaml | 1 + .../0000_50_olm_01-networkpolicies.yaml | 14 ++++++++++++++ 4 files changed, 53 insertions(+) create mode 100644 manifests/0000_50_olm_01-networkpolicies.yaml create mode 100644 microshift-manifests/0000_50_olm_01-networkpolicies.yaml create mode 100644 staging/operator-lifecycle-manager/deploy/chart/templates/0000_50_olm_01-networkpolicies.yaml diff --git a/manifests/0000_50_olm_01-networkpolicies.yaml b/manifests/0000_50_olm_01-networkpolicies.yaml new file mode 100644 index 0000000000..6b2f09306e --- /dev/null +++ b/manifests/0000_50_olm_01-networkpolicies.yaml @@ -0,0 +1,19 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-allow-all + namespace: openshift-operators + annotations: + include.release.openshift.io/ibm-cloud-managed: "true" + include.release.openshift.io/self-managed-high-availability: "true" + capability.openshift.io/name: "OperatorLifecycleManager" + include.release.openshift.io/hypershift: "true" +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress + ingress: + - {} + egress: + - {} diff --git a/microshift-manifests/0000_50_olm_01-networkpolicies.yaml b/microshift-manifests/0000_50_olm_01-networkpolicies.yaml new file mode 100644 index 0000000000..6b2f09306e --- /dev/null +++ b/microshift-manifests/0000_50_olm_01-networkpolicies.yaml @@ -0,0 +1,19 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-allow-all + namespace: openshift-operators + annotations: + include.release.openshift.io/ibm-cloud-managed: "true" + include.release.openshift.io/self-managed-high-availability: "true" + capability.openshift.io/name: "OperatorLifecycleManager" + include.release.openshift.io/hypershift: "true" +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress + ingress: + - {} + egress: + - {} diff --git a/microshift-manifests/kustomization.yaml b/microshift-manifests/kustomization.yaml index ea1c55c54e..f329246f4c 100644 --- a/microshift-manifests/kustomization.yaml +++ b/microshift-manifests/kustomization.yaml @@ -15,6 +15,7 @@ resources: - 0000_50_olm_00-pprof-secret.yaml - 0000_50_olm_00-subscriptions.crd.yaml - 0000_50_olm_01-olm-operator.serviceaccount.yaml + - 0000_50_olm_01-networkpolicies.yaml - 0000_50_olm_02-olmconfig.yaml - 0000_50_olm_02-services.yaml - 0000_50_olm_07-olm-operator.deployment.yaml diff --git a/staging/operator-lifecycle-manager/deploy/chart/templates/0000_50_olm_01-networkpolicies.yaml b/staging/operator-lifecycle-manager/deploy/chart/templates/0000_50_olm_01-networkpolicies.yaml new file mode 100644 index 0000000000..cb0da2493d --- /dev/null +++ b/staging/operator-lifecycle-manager/deploy/chart/templates/0000_50_olm_01-networkpolicies.yaml @@ -0,0 +1,14 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-allow-all + namespace: {{ .Values.operator_namespace }} +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress + ingress: + - {} + egress: + - {} From 5fa221329cfeb940032fd0ca206468dc98a40478 Mon Sep 17 00:00:00 2001 From: Anik Bhattacharjee Date: Mon, 18 Aug 2025 14:39:30 -0400 Subject: [PATCH 4/6] Upstream: : regenerate manifests --- manifests/0000_50_olm_01-networkpolicies.yaml | 2 +- microshift-manifests/0000_50_olm_01-networkpolicies.yaml | 2 +- microshift-manifests/kustomization.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/manifests/0000_50_olm_01-networkpolicies.yaml b/manifests/0000_50_olm_01-networkpolicies.yaml index 6b2f09306e..84fb60d38d 100644 --- a/manifests/0000_50_olm_01-networkpolicies.yaml +++ b/manifests/0000_50_olm_01-networkpolicies.yaml @@ -5,9 +5,9 @@ metadata: namespace: openshift-operators annotations: include.release.openshift.io/ibm-cloud-managed: "true" + include.release.openshift.io/hypershift: "true" include.release.openshift.io/self-managed-high-availability: "true" capability.openshift.io/name: "OperatorLifecycleManager" - include.release.openshift.io/hypershift: "true" spec: podSelector: {} policyTypes: diff --git a/microshift-manifests/0000_50_olm_01-networkpolicies.yaml b/microshift-manifests/0000_50_olm_01-networkpolicies.yaml index 6b2f09306e..84fb60d38d 100644 --- a/microshift-manifests/0000_50_olm_01-networkpolicies.yaml +++ b/microshift-manifests/0000_50_olm_01-networkpolicies.yaml @@ -5,9 +5,9 @@ metadata: namespace: openshift-operators annotations: include.release.openshift.io/ibm-cloud-managed: "true" + include.release.openshift.io/hypershift: "true" include.release.openshift.io/self-managed-high-availability: "true" capability.openshift.io/name: "OperatorLifecycleManager" - include.release.openshift.io/hypershift: "true" spec: podSelector: {} policyTypes: diff --git a/microshift-manifests/kustomization.yaml b/microshift-manifests/kustomization.yaml index f329246f4c..2147cc0b9c 100644 --- a/microshift-manifests/kustomization.yaml +++ b/microshift-manifests/kustomization.yaml @@ -14,8 +14,8 @@ resources: - 0000_50_olm_00-pprof-rbac.yaml - 0000_50_olm_00-pprof-secret.yaml - 0000_50_olm_00-subscriptions.crd.yaml - - 0000_50_olm_01-olm-operator.serviceaccount.yaml - 0000_50_olm_01-networkpolicies.yaml + - 0000_50_olm_01-olm-operator.serviceaccount.yaml - 0000_50_olm_02-olmconfig.yaml - 0000_50_olm_02-services.yaml - 0000_50_olm_07-olm-operator.deployment.yaml From be664de5d1c91fec3d3945c3c2ac23ae319a475f Mon Sep 17 00:00:00 2001 From: Per Goncalves da Silva Date: Fri, 5 Sep 2025 13:17:09 +0200 Subject: [PATCH 5/6] [WIP] Test: relax polling Signed-off-by: Per Goncalves da Silva --- .../test/e2e/collect-ci-artifacts.sh | 1 + .../test/e2e/subscription_e2e_test.go | 7050 ++++++++--------- .../test/e2e/util.go | 1840 ++--- 3 files changed, 4446 insertions(+), 4445 deletions(-) diff --git a/staging/operator-lifecycle-manager/test/e2e/collect-ci-artifacts.sh b/staging/operator-lifecycle-manager/test/e2e/collect-ci-artifacts.sh index 813c162695..9188408b98 100755 --- a/staging/operator-lifecycle-manager/test/e2e/collect-ci-artifacts.sh +++ b/staging/operator-lifecycle-manager/test/e2e/collect-ci-artifacts.sh @@ -20,6 +20,7 @@ commands+=("get operatorgroups -o yaml") commands+=("get clusterserviceversions -o yaml") commands+=("get installplans -o yaml") commands+=("get pods -o wide") +commands+=("get jobs -o wide") commands+=("get events --sort-by .lastTimestamp") echo "Storing the test artifact output in the ${TEST_ARTIFACTS_DIR} directory" diff --git a/staging/operator-lifecycle-manager/test/e2e/subscription_e2e_test.go b/staging/operator-lifecycle-manager/test/e2e/subscription_e2e_test.go index c5ed6eff6c..b516cfea68 100644 --- a/staging/operator-lifecycle-manager/test/e2e/subscription_e2e_test.go +++ b/staging/operator-lifecycle-manager/test/e2e/subscription_e2e_test.go @@ -1,2761 +1,2761 @@ package e2e import ( - "context" - "encoding/json" - "fmt" - "os" - "path/filepath" - "reflect" - "strings" - "sync" - "time" - - "github.com/blang/semver/v4" - "github.com/ghodss/yaml" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - configv1 "github.com/openshift/api/config/v1" - configv1client "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1" - "github.com/stretchr/testify/require" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "k8s.io/apimachinery/pkg/api/equality" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/client-go/discovery" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/operator-framework/api/pkg/lib/version" - operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" - operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" - "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" - "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install" - "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" - "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/projection" - "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient" - "github.com/operator-framework/operator-lifecycle-manager/test/e2e/ctx" - registryapi "github.com/operator-framework/operator-registry/pkg/api" + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "sync" + "time" + + "github.com/blang/semver/v4" + "github.com/ghodss/yaml" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + configv1 "github.com/openshift/api/config/v1" + configv1client "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1" + "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/api/equality" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/discovery" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/operator-framework/api/pkg/lib/version" + operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" + operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/projection" + "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient" + "github.com/operator-framework/operator-lifecycle-manager/test/e2e/ctx" + registryapi "github.com/operator-framework/operator-registry/pkg/api" ) func Step(level int, text string, callbacks ...func()) { - By(strings.Repeat(" ", level*2)+text, callbacks...) + By(strings.Repeat(" ", level*2)+text, callbacks...) } const ( - timeout = time.Second * 20 - interval = time.Millisecond * 100 + timeout = time.Second * 20 + interval = time.Millisecond * 100 ) const ( - subscriptionTestDataBaseDir = "subscription/" + subscriptionTestDataBaseDir = "subscription/" ) var _ = Describe("Subscription", func() { - var ( - generatedNamespace corev1.Namespace - operatorGroup operatorsv1.OperatorGroup - c operatorclient.ClientInterface - crc versioned.Interface - ) - - BeforeEach(func() { - c = ctx.Ctx().KubeClient() - crc = ctx.Ctx().OperatorClient() - - nsName := genName("subscription-e2e-") - operatorGroup = operatorsv1.OperatorGroup{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-operatorgroup", nsName), - Namespace: nsName, - }, - } - generatedNamespace = SetupGeneratedTestNamespaceWithOperatorGroup(nsName, operatorGroup) - }) - - AfterEach(func() { - TeardownNamespace(generatedNamespace.GetName()) - }) - - When("an entry in the middle of a channel does not provide a required GVK", func() { - var ( - teardown func() - ) - - BeforeEach(func() { - teardown = func() {} - packages := []registry.PackageManifest{ - { - PackageName: "dependency", - Channels: []registry.PackageChannel{ - {Name: "channel-dependency", CurrentCSVName: "csv-dependency-3"}, - }, - DefaultChannelName: "channel-dependency", - }, - { - PackageName: "root", - Channels: []registry.PackageChannel{ - {Name: "channel-root", CurrentCSVName: "csv-root"}, - }, - DefaultChannelName: "channel-root", - }, - } - - crds := []apiextensionsv1.CustomResourceDefinition{newCRD(genName("crd-"))} - csvs := []operatorsv1alpha1.ClusterServiceVersion{ - newCSV("csv-dependency-1", generatedNamespace.GetName(), "", semver.MustParse("1.0.0"), crds, nil, nil), - newCSV("csv-dependency-2", generatedNamespace.GetName(), "csv-dependency-1", semver.MustParse("2.0.0"), nil, nil, nil), - newCSV("csv-dependency-3", generatedNamespace.GetName(), "csv-dependency-2", semver.MustParse("3.0.0"), crds, nil, nil), - newCSV("csv-root", generatedNamespace.GetName(), "", semver.MustParse("1.0.0"), nil, crds, nil), - } - - _, teardown = createInternalCatalogSource(ctx.Ctx().KubeClient(), ctx.Ctx().OperatorClient(), "test-catalog", generatedNamespace.GetName(), packages, crds, csvs) - _, err := fetchCatalogSourceOnStatus(ctx.Ctx().OperatorClient(), "test-catalog", generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) - Expect(err).NotTo(HaveOccurred()) - - createSubscriptionForCatalog(ctx.Ctx().OperatorClient(), generatedNamespace.GetName(), "test-subscription", "test-catalog", "root", "channel-root", "", operatorsv1alpha1.ApprovalAutomatic) - }) - - AfterEach(func() { - teardown() - }) - - It("should create a Subscription for the latest entry providing the required GVK", func() { - Eventually(func() ([]operatorsv1alpha1.Subscription, error) { - var list operatorsv1alpha1.SubscriptionList - if err := ctx.Ctx().Client().List(context.Background(), &list); err != nil { - return nil, err - } - return list.Items, nil - }).Should(ContainElement(WithTransform( - func(in operatorsv1alpha1.Subscription) operatorsv1alpha1.SubscriptionSpec { - return operatorsv1alpha1.SubscriptionSpec{ - CatalogSource: in.Spec.CatalogSource, - CatalogSourceNamespace: in.Spec.CatalogSourceNamespace, - Package: in.Spec.Package, - Channel: in.Spec.Channel, - StartingCSV: in.Spec.StartingCSV, - } - }, - Equal(operatorsv1alpha1.SubscriptionSpec{ - CatalogSource: "test-catalog", - CatalogSourceNamespace: generatedNamespace.GetName(), - Package: "dependency", - Channel: "channel-dependency", - StartingCSV: "csv-dependency-3", - }), - ))) - }) - }) - - It("creation if not installed", func() { - By(` I. Creating a new subscription`) - By(` A. If package is not installed, creating a subscription should install latest version`) - - defer func() { - if env := os.Getenv("SKIP_CLEANUP"); env != "" { - fmt.Printf("Skipping cleanup of subscriptions in namespace %s\n", generatedNamespace.GetName()) - return - } - require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{})) - }() - - By("creating a catalog") - require.NoError(GinkgoT(), initCatalog(GinkgoT(), generatedNamespace.GetName(), c, crc)) - - By(fmt.Sprintf("creating a subscription: %s/%s", generatedNamespace.GetName(), testSubscriptionName)) - cleanup, _ := createSubscription(GinkgoT(), crc, generatedNamespace.GetName(), testSubscriptionName, testPackageName, betaChannel, operatorsv1alpha1.ApprovalAutomatic) - - defer cleanup() - - By("waiting for the subscription to have a current CSV and be at latest") - var currentCSV string - Eventually(func() bool { - fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), testSubscriptionName, metav1.GetOptions{}) - if err != nil { - return false - } - if fetched != nil { - currentCSV = fetched.Status.CurrentCSV - return fetched.Status.State == operatorsv1alpha1.SubscriptionStateAtLatest - } - return false - }, 5*time.Minute, 10*time.Second).Should(BeTrue()) - - csv, err := fetchCSV(crc, generatedNamespace.GetName(), currentCSV, buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded)) - require.NoError(GinkgoT(), err) - - By(`Check for the olm.package property as a proxy for`) - By(`verifying that the annotation value is reasonable.`) - Expect( - projection.PropertyListFromPropertiesAnnotation(csv.GetAnnotations()["operatorframework.io/properties"]), - ).To(ContainElement( - ®istryapi.Property{Type: "olm.package", Value: `{"packageName":"myapp","version":"0.1.1"}`}, - )) - }) - - It("creation using existing CSV", func() { - By(` I. Creating a new subscription`) - By(` B. If package is already installed, creating a subscription should upgrade it to the latest version`) - - defer func() { - require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{})) - }() - require.NoError(GinkgoT(), initCatalog(GinkgoT(), generatedNamespace.GetName(), c, crc)) - - By(`Will be cleaned up by the upgrade process`) - _, err := createCSV(c, crc, stableCSV, generatedNamespace.GetName(), false, false) - require.NoError(GinkgoT(), err) - - subscriptionCleanup, _ := createSubscription(GinkgoT(), crc, generatedNamespace.GetName(), testSubscriptionName, testPackageName, alphaChannel, operatorsv1alpha1.ApprovalAutomatic) - defer subscriptionCleanup() - - subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), testSubscriptionName, subscriptionStateAtLatestChecker()) - require.NoError(GinkgoT(), err) - require.NotNil(GinkgoT(), subscription) - _, err = fetchCSV(crc, generatedNamespace.GetName(), subscription.Status.CurrentCSV, buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded)) - require.NoError(GinkgoT(), err) - }) - It("skip range", func() { - - crdPlural := genName("ins") - crdName := crdPlural + ".cluster.com" - crd := apiextensionsv1.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: crdName, - }, - Spec: apiextensionsv1.CustomResourceDefinitionSpec{ - Group: "cluster.com", - Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ - { - Name: "v1alpha1", - Served: true, - Storage: true, - Schema: &apiextensionsv1.CustomResourceValidation{ - OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ - Type: "object", - Description: "my crd schema", - }, - }, - }, - }, - Names: apiextensionsv1.CustomResourceDefinitionNames{ - Plural: crdPlural, - Singular: crdPlural, - Kind: crdPlural, - ListKind: "list" + crdPlural, - }, - Scope: apiextensionsv1.NamespaceScoped, - }, - } - - mainPackageName := genName("nginx-") - mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName) - updatedPackageStable := fmt.Sprintf("%s-updated", mainPackageName) - stableChannel := "stable" - mainCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0-1556661347"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) - updatedCSV := newCSV(updatedPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0-1556661832"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) - updatedCSV.SetAnnotations(map[string]string{"olm.skipRange": ">=0.1.0-1556661347 <0.1.0-1556661832"}) - - c := newKubeClient() - crc := newCRClient() - defer func() { - require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{})) - }() - - mainCatalogName := genName("mock-ocs-main-") - - By(`Create separate manifests for each CatalogSource`) - mainManifests := []registry.PackageManifest{ - { - PackageName: mainPackageName, - Channels: []registry.PackageChannel{ - {Name: stableChannel, CurrentCSVName: mainPackageStable}, - }, - DefaultChannelName: stableChannel, - }, - } - updatedManifests := []registry.PackageManifest{ - { - PackageName: mainPackageName, - Channels: []registry.PackageChannel{ - {Name: stableChannel, CurrentCSVName: updatedPackageStable}, - }, - DefaultChannelName: stableChannel, - }, - } - - By(`Create catalog source`) - _, cleanupMainCatalogSource := createInternalCatalogSource(c, crc, mainCatalogName, generatedNamespace.GetName(), mainManifests, []apiextensionsv1.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{mainCSV}) - defer cleanupMainCatalogSource() - By(`Attempt to get the catalog source before creating subscription`) - _, err := fetchCatalogSourceOnStatus(crc, mainCatalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) - require.NoError(GinkgoT(), err) - - By(`Create a subscription`) - subscriptionName := genName("sub-nginx-") - subscriptionCleanup := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) - defer subscriptionCleanup() - - By(`Wait for csv to install`) - firstCSV, err := fetchCSV(crc, generatedNamespace.GetName(), mainCSV.GetName(), csvSucceededChecker) - require.NoError(GinkgoT(), err) - - By(`Update catalog with a new csv in the channel with a skip range`) - updateInternalCatalog(GinkgoT(), c, crc, mainCatalogName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{updatedCSV}, updatedManifests) - - By(`Wait for csv to update`) - finalCSV, err := fetchCSV(crc, generatedNamespace.GetName(), updatedCSV.GetName(), csvSucceededChecker) - require.NoError(GinkgoT(), err) - - By(`Ensure we set the replacement field based on the registry data`) - require.Equal(GinkgoT(), firstCSV.GetName(), finalCSV.Spec.Replaces) - }) - - It("creation manual approval", func() { - By(`If installPlanApproval is set to manual, the installplans created should be created with approval: manual`) - - defer func() { - require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{})) - }() - require.NoError(GinkgoT(), initCatalog(GinkgoT(), generatedNamespace.GetName(), c, crc)) - - subscriptionCleanup, _ := createSubscription(GinkgoT(), crc, generatedNamespace.GetName(), "manual-subscription", testPackageName, stableChannel, operatorsv1alpha1.ApprovalManual) - defer subscriptionCleanup() - - subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), "manual-subscription", subscriptionHasCondition(operatorsv1alpha1.SubscriptionInstallPlanPending, corev1.ConditionTrue, string(operatorsv1alpha1.InstallPlanPhaseRequiresApproval), "")) - require.NoError(GinkgoT(), err) - require.NotNil(GinkgoT(), subscription) - - installPlan, err := fetchInstallPlanWithNamespace(GinkgoT(), crc, subscription.Status.Install.Name, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseRequiresApproval)) - require.NoError(GinkgoT(), err) - require.NotNil(GinkgoT(), installPlan) - - require.Equal(GinkgoT(), operatorsv1alpha1.ApprovalManual, installPlan.Spec.Approval) - require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseRequiresApproval, installPlan.Status.Phase) - - By(`Delete the current installplan`) - err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Delete(context.Background(), installPlan.Name, metav1.DeleteOptions{}) - require.NoError(GinkgoT(), err) - - var ipName string - Eventually(func() bool { - fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), "manual-subscription", metav1.GetOptions{}) - if err != nil { - return false - } - if fetched.Status.Install != nil { - ipName = fetched.Status.Install.Name - return fetched.Status.Install.Name != installPlan.Name - } - return false - }, 5*time.Minute, 10*time.Second).Should(BeTrue()) - - By(`Fetch new installplan`) - newInstallPlan, err := fetchInstallPlanWithNamespace(GinkgoT(), crc, ipName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseRequiresApproval)) - require.NoError(GinkgoT(), err) - require.NotNil(GinkgoT(), newInstallPlan) - - require.NotEqual(GinkgoT(), installPlan.Name, newInstallPlan.Name, "expected new installplan recreated") - require.Equal(GinkgoT(), operatorsv1alpha1.ApprovalManual, newInstallPlan.Spec.Approval) - require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseRequiresApproval, newInstallPlan.Status.Phase) - - By(`Set the InstallPlan's approved to True`) - Eventually(Apply(newInstallPlan, func(p *operatorsv1alpha1.InstallPlan) error { - - p.Spec.Approved = true - return nil - })).Should(Succeed()) - - subscription, err = fetchSubscription(crc, generatedNamespace.GetName(), "manual-subscription", subscriptionStateAtLatestChecker()) - require.NoError(GinkgoT(), err) - require.NotNil(GinkgoT(), subscription) - - _, err = fetchCSV(crc, generatedNamespace.GetName(), subscription.Status.CurrentCSV, buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded)) - require.NoError(GinkgoT(), err) - }) - - It("with starting CSV", func() { - - crdPlural := genName("ins") - crdName := crdPlural + ".cluster.com" - - crd := apiextensionsv1.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: crdName, - }, - Spec: apiextensionsv1.CustomResourceDefinitionSpec{ - Group: "cluster.com", - Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ - { - Name: "v1alpha1", - Served: true, - Storage: true, - Schema: &apiextensionsv1.CustomResourceValidation{ - OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ - Type: "object", - Description: "my crd schema", - }, - }, - }, - }, - Names: apiextensionsv1.CustomResourceDefinitionNames{ - Plural: crdPlural, - Singular: crdPlural, - Kind: crdPlural, - ListKind: "list" + crdPlural, - }, - Scope: apiextensionsv1.NamespaceScoped, - }, - } - - By(`Create CSV`) - packageName := genName("nginx-") - stableChannel := "stable" - - csvA := newCSV("nginx-a", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) - csvB := newCSV("nginx-b", generatedNamespace.GetName(), "nginx-a", semver.MustParse("0.2.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) - - By(`Create PackageManifests`) - manifests := []registry.PackageManifest{ - { - PackageName: packageName, - Channels: []registry.PackageChannel{ - {Name: stableChannel, CurrentCSVName: csvB.GetName()}, - }, - DefaultChannelName: stableChannel, - }, - } - - By(`Create the CatalogSource`) - catalogSourceName := genName("mock-nginx-") - _, cleanupCatalogSource := createInternalCatalogSource(c, crc, catalogSourceName, generatedNamespace.GetName(), manifests, []apiextensionsv1.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{csvA, csvB}) - defer cleanupCatalogSource() - - By(`Attempt to get the catalog source before creating install plan`) - _, err := fetchCatalogSourceOnStatus(crc, catalogSourceName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) - require.NoError(GinkgoT(), err) - - subscriptionName := genName("sub-nginx-") - cleanupSubscription := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, catalogSourceName, packageName, stableChannel, csvA.GetName(), operatorsv1alpha1.ApprovalManual) - defer cleanupSubscription() - - subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) - require.NoError(GinkgoT(), err) - require.NotNil(GinkgoT(), subscription) - - installPlanName := subscription.Status.Install.Name - - By(`Wait for InstallPlan to be status: Complete before checking resource presence`) - requiresApprovalChecker := buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseRequiresApproval) - fetchedInstallPlan, err := fetchInstallPlanWithNamespace(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), requiresApprovalChecker) - require.NoError(GinkgoT(), err) - - By(`Ensure that only 1 installplan was created`) - ips, err := crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).List(context.Background(), metav1.ListOptions{}) - require.NoError(GinkgoT(), err) - require.Len(GinkgoT(), ips.Items, 1) - - By(`Ensure that csvA and its crd are found in the plan`) - csvFound := false - crdFound := false - for _, s := range fetchedInstallPlan.Status.Plan { - require.Equal(GinkgoT(), csvA.GetName(), s.Resolving, "unexpected resolution found") - require.Equal(GinkgoT(), operatorsv1alpha1.StepStatusUnknown, s.Status, "status should be unknown") - require.Equal(GinkgoT(), catalogSourceName, s.Resource.CatalogSource, "incorrect catalogsource on step resource") - switch kind := s.Resource.Kind; kind { - case operatorsv1alpha1.ClusterServiceVersionKind: - require.Equal(GinkgoT(), csvA.GetName(), s.Resource.Name, "unexpected csv found") - csvFound = true - case "CustomResourceDefinition": - require.Equal(GinkgoT(), crdName, s.Resource.Name, "unexpected crd found") - crdFound = true - default: - GinkgoT().Fatalf("unexpected resource kind found in installplan: %s", kind) - } - } - require.True(GinkgoT(), csvFound, "expected csv not found in installplan") - require.True(GinkgoT(), crdFound, "expected crd not found in installplan") - - By(`Ensure that csvB is not found in the plan`) - csvFound = false - for _, s := range fetchedInstallPlan.Status.Plan { - require.Equal(GinkgoT(), csvA.GetName(), s.Resolving, "unexpected resolution found") - require.Equal(GinkgoT(), operatorsv1alpha1.StepStatusUnknown, s.Status, "status should be unknown") - require.Equal(GinkgoT(), catalogSourceName, s.Resource.CatalogSource, "incorrect catalogsource on step resource") - switch kind := s.Resource.Kind; kind { - case operatorsv1alpha1.ClusterServiceVersionKind: - if s.Resource.Name == csvB.GetName() { - csvFound = true - } - } - } - require.False(GinkgoT(), csvFound, "expected csv not found in installplan") - - By(`Approve the installplan and wait for csvA to be installed`) - fetchedInstallPlan.Spec.Approved = true - _, err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Update(context.Background(), fetchedInstallPlan, metav1.UpdateOptions{}) - require.NoError(GinkgoT(), err) - - _, err = fetchCSV(crc, generatedNamespace.GetName(), csvA.GetName(), csvSucceededChecker) - require.NoError(GinkgoT(), err) - - By(`Wait for the subscription to begin upgrading to csvB`) - By(`The upgrade changes the installplanref on the subscription`) - Eventually(func() (bool, error) { - subscription, err = crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), subscriptionName, metav1.GetOptions{}) - return subscription != nil && subscription.Status.InstallPlanRef.Name != fetchedInstallPlan.GetName() && subscription.Status.State == operatorsv1alpha1.SubscriptionStateUpgradePending, err - }, 5*time.Minute, 1*time.Second).Should(BeTrue(), "expected new installplan for upgraded csv") - - upgradeInstallPlan, err := fetchInstallPlanWithNamespace(GinkgoT(), crc, subscription.Status.InstallPlanRef.Name, generatedNamespace.GetName(), requiresApprovalChecker) - require.NoError(GinkgoT(), err) - - By(`Approve the upgrade installplan and wait for`) - upgradeInstallPlan.Spec.Approved = true - _, err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Update(context.Background(), upgradeInstallPlan, metav1.UpdateOptions{}) - require.NoError(GinkgoT(), err) - - _, err = fetchCSV(crc, generatedNamespace.GetName(), csvB.GetName(), csvSucceededChecker) - require.NoError(GinkgoT(), err) - - By(`Ensure that 2 installplans were created`) - ips, err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).List(context.Background(), metav1.ListOptions{}) - require.NoError(GinkgoT(), err) - require.Len(GinkgoT(), ips.Items, 2) - }) - - It("[FLAKE] updates multiple intermediates", func() { - By(`issue: https://github.com/operator-framework/operator-lifecycle-manager/issues/2635`) - - crd := newCRD("ins") - - By(`Create CSV`) - packageName := genName("nginx-") - stableChannel := "stable" - - csvA := newCSV("nginx-a", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) - csvB := newCSV("nginx-b", generatedNamespace.GetName(), "nginx-a", semver.MustParse("0.2.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) - csvC := newCSV("nginx-c", generatedNamespace.GetName(), "nginx-b", semver.MustParse("0.3.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) - - By(`Create PackageManifests`) - manifests := []registry.PackageManifest{ - { - PackageName: packageName, - Channels: []registry.PackageChannel{ - {Name: stableChannel, CurrentCSVName: csvA.GetName()}, - }, - DefaultChannelName: stableChannel, - }, - } - - By(`Create the CatalogSource with just one version`) - catalogSourceName := genName("mock-nginx-") - _, cleanupCatalogSource := createInternalCatalogSource(c, crc, catalogSourceName, generatedNamespace.GetName(), manifests, []apiextensionsv1.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{csvA}) - defer cleanupCatalogSource() - - By(`Attempt to get the catalog source before creating install plan`) - _, err := fetchCatalogSourceOnStatus(crc, catalogSourceName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) - require.NoError(GinkgoT(), err) - - subscriptionName := genName("sub-nginx-") - cleanupSubscription := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, catalogSourceName, packageName, stableChannel, csvA.GetName(), operatorsv1alpha1.ApprovalAutomatic) - defer cleanupSubscription() - - subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) - require.NoError(GinkgoT(), err) - require.NotNil(GinkgoT(), subscription) - - By(`Wait for csvA to be installed`) - _, err = fetchCSV(crc, generatedNamespace.GetName(), csvA.GetName(), csvSucceededChecker) - require.NoError(GinkgoT(), err) - - By(`Set up async watches that will fail the test if csvB doesn't get created in between csvA and csvC`) - var wg sync.WaitGroup - go func(t GinkgoTInterface) { - defer GinkgoRecover() - wg.Add(1) - defer wg.Done() - _, err := fetchCSV(crc, generatedNamespace.GetName(), csvB.GetName(), csvReplacingChecker) - require.NoError(GinkgoT(), err) - }(GinkgoT()) - By(`Update the catalog to include multiple updates`) - packages := []registry.PackageManifest{ - { - PackageName: packageName, - Channels: []registry.PackageChannel{ - {Name: stableChannel, CurrentCSVName: csvC.GetName()}, - }, - DefaultChannelName: stableChannel, - }, - } - - updateInternalCatalog(GinkgoT(), c, crc, catalogSourceName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{csvA, csvB, csvC}, packages) - - By(`wait for checks on intermediate csvs to succeed`) - wg.Wait() - - By(`Wait for csvC to be installed`) - _, err = fetchCSV(crc, generatedNamespace.GetName(), csvC.GetName(), csvSucceededChecker) - require.NoError(GinkgoT(), err) - - By(`Should eventually GC the CSVs`) - err = waitForCsvToDelete(generatedNamespace.GetName(), csvA.Name, crc) - Expect(err).ShouldNot(HaveOccurred()) - - err = waitForCsvToDelete(generatedNamespace.GetName(), csvB.Name, crc) - Expect(err).ShouldNot(HaveOccurred()) - - By(`TODO: check installplans, subscription status, etc`) - }) - - It("updates existing install plan", func() { - By(`TestSubscriptionUpdatesExistingInstallPlan ensures that an existing InstallPlan has the appropriate approval requirement from Subscription.`) - - Skip("ToDo: This test was skipped before ginkgo conversion") - - By(`Create CSV`) - packageName := genName("nginx-") - stableChannel := "stable" - - csvA := newCSV("nginx-a", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), nil, nil, nil) - csvB := newCSV("nginx-b", generatedNamespace.GetName(), "nginx-a", semver.MustParse("0.2.0"), nil, nil, nil) - - By(`Create PackageManifests`) - manifests := []registry.PackageManifest{ - { - PackageName: packageName, - Channels: []registry.PackageChannel{ - {Name: stableChannel, CurrentCSVName: csvB.GetName()}, - }, - DefaultChannelName: stableChannel, - }, - } - - By(`Create the CatalogSource with just one version`) - catalogSourceName := genName("mock-nginx-") - _, cleanupCatalogSource := createInternalCatalogSource(c, crc, catalogSourceName, generatedNamespace.GetName(), manifests, nil, []operatorsv1alpha1.ClusterServiceVersion{csvA, csvB}) - defer cleanupCatalogSource() - - By(`Attempt to get the catalog source before creating install plan`) - _, err := fetchCatalogSourceOnStatus(crc, catalogSourceName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) - require.NoError(GinkgoT(), err) - - By(`Create a subscription to just get an InstallPlan for csvB`) - subscriptionName := genName("sub-nginx-") - createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, catalogSourceName, packageName, stableChannel, csvB.GetName(), operatorsv1alpha1.ApprovalAutomatic) - - By(`Wait for csvB to be installed`) - _, err = fetchCSV(crc, generatedNamespace.GetName(), csvB.GetName(), csvSucceededChecker) - require.NoError(GinkgoT(), err) - - subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) - fetchedInstallPlan, err := fetchInstallPlanWithNamespace(GinkgoT(), crc, subscription.Status.InstallPlanRef.Name, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete)) - - By(`Delete this subscription`) - err = crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), *metav1.NewDeleteOptions(0), metav1.ListOptions{}) - require.NoError(GinkgoT(), err) - By(`Delete orphaned csvB`) - require.NoError(GinkgoT(), crc.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Delete(context.Background(), csvB.GetName(), metav1.DeleteOptions{})) - - By(`Create an InstallPlan for csvB`) - ip := &operatorsv1alpha1.InstallPlan{ - ObjectMeta: metav1.ObjectMeta{ - GenerateName: "install-", - Namespace: generatedNamespace.GetName(), - }, - Spec: operatorsv1alpha1.InstallPlanSpec{ - ClusterServiceVersionNames: []string{csvB.GetName()}, - Approval: operatorsv1alpha1.ApprovalAutomatic, - Approved: false, - }, - } - ip2, err := crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Create(context.Background(), ip, metav1.CreateOptions{}) - require.NoError(GinkgoT(), err) - - ip2.Status = operatorsv1alpha1.InstallPlanStatus{ - Plan: fetchedInstallPlan.Status.Plan, - CatalogSources: []string{catalogSourceName}, - } - - _, err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).UpdateStatus(context.Background(), ip2, metav1.UpdateOptions{}) - require.NoError(GinkgoT(), err) - - subscriptionName = genName("sub-nginx-") - cleanupSubscription := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, catalogSourceName, packageName, stableChannel, csvA.GetName(), operatorsv1alpha1.ApprovalManual) - defer cleanupSubscription() - - subscription, err = fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) - require.NoError(GinkgoT(), err) - require.NotNil(GinkgoT(), subscription) - - installPlanName := subscription.Status.Install.Name - - By(`Wait for InstallPlan to be status: Complete before checking resource presence`) - requiresApprovalChecker := buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseRequiresApproval) - fetchedInstallPlan, err = fetchInstallPlanWithNamespace(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), requiresApprovalChecker) - require.NoError(GinkgoT(), err) - - By(`Approve the installplan and wait for csvA to be installed`) - fetchedInstallPlan.Spec.Approved = true - _, err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Update(context.Background(), fetchedInstallPlan, metav1.UpdateOptions{}) - require.NoError(GinkgoT(), err) - - By(`Wait for csvA to be installed`) - _, err = fetchCSV(crc, generatedNamespace.GetName(), csvA.GetName(), csvSucceededChecker) - require.NoError(GinkgoT(), err) - - By(`Wait for the subscription to begin upgrading to csvB`) - subscription, err = fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionStateUpgradePendingChecker()) - require.NoError(GinkgoT(), err) - - By(`Fetch existing csvB installPlan`) - fetchedInstallPlan, err = fetchInstallPlanWithNamespace(GinkgoT(), crc, subscription.Status.InstallPlanRef.Name, generatedNamespace.GetName(), requiresApprovalChecker) - require.NoError(GinkgoT(), err) - require.Equal(GinkgoT(), ip2.GetName(), subscription.Status.InstallPlanRef.Name, "expected new installplan is the same with pre-exising one") - - By(`Approve the installplan and wait for csvB to be installed`) - fetchedInstallPlan.Spec.Approved = true - _, err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Update(context.Background(), fetchedInstallPlan, metav1.UpdateOptions{}) - require.NoError(GinkgoT(), err) - - By(`Wait for csvB to be installed`) - _, err = fetchCSV(crc, generatedNamespace.GetName(), csvB.GetName(), csvSucceededChecker) - require.NoError(GinkgoT(), err) - }) - - Describe("puppeting CatalogSource health status", func() { - var ( - getOpts metav1.GetOptions - deleteOpts *metav1.DeleteOptions - ) - - BeforeEach(func() { - getOpts = metav1.GetOptions{} - deleteOpts = &metav1.DeleteOptions{} - }) - - AfterEach(func() { - err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{}) - Expect(err).NotTo(HaveOccurred()) - }) - - When("missing target catalog", func() { - It("should surface the missing catalog", func() { - By(`TestSubscriptionStatusMissingTargetCatalogSource ensures that a Subscription has the appropriate status condition when`) - By(`its target catalog is missing.`) - By(` BySteps:`) - By(`1. Generate an initial CatalogSource in the target namespace`) - By(`2. Generate Subscription, "sub", targetting non-existent CatalogSource, "missing"`) - By(`3. Wait for sub status to show SubscriptionCatalogSourcesUnhealthy with status True, reason CatalogSourcesUpdated, and appropriate missing message`) - By(`4. Update sub's spec to target the "mysubscription"`) - By(`5. Wait for sub's status to show SubscriptionCatalogSourcesUnhealthy with status False, reason AllCatalogSourcesHealthy, and reason "all available catalogsources are healthy"`) - By(`6. Wait for sub to succeed`) - err := initCatalog(GinkgoT(), generatedNamespace.GetName(), c, crc) - Expect(err).NotTo(HaveOccurred()) - - missingName := "missing" - cleanup := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), testSubscriptionName, missingName, testPackageName, betaChannel, "", operatorsv1alpha1.ApprovalAutomatic) - defer cleanup() - - By("detecting its absence") - sub, err := fetchSubscription(crc, generatedNamespace.GetName(), testSubscriptionName, subscriptionHasCondition(operatorsv1alpha1.SubscriptionCatalogSourcesUnhealthy, corev1.ConditionTrue, operatorsv1alpha1.UnhealthyCatalogSourceFound, fmt.Sprintf("targeted catalogsource %s/%s missing", generatedNamespace.GetName(), missingName))) - Expect(err).NotTo(HaveOccurred()) - Expect(sub).ToNot(BeNil()) - - By("updating the subscription to target an existing catsrc") - Eventually(func() error { - sub, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), testSubscriptionName, metav1.GetOptions{}) - if err != nil { - return err - } - if sub == nil { - return fmt.Errorf("subscription is nil") - } - sub.Spec.CatalogSource = catalogSourceName - _, err = crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Update(context.Background(), sub, metav1.UpdateOptions{}) - return err - }).Should(Succeed()) - - By(`Wait for SubscriptionCatalogSourcesUnhealthy to be false`) - By("detecting a new existing target") - _, err = fetchSubscription(crc, generatedNamespace.GetName(), testSubscriptionName, subscriptionHasCondition(operatorsv1alpha1.SubscriptionCatalogSourcesUnhealthy, corev1.ConditionFalse, operatorsv1alpha1.AllCatalogSourcesHealthy, "all available catalogsources are healthy")) - Expect(err).NotTo(HaveOccurred()) - - By(`Wait for success`) - _, err = fetchSubscription(crc, generatedNamespace.GetName(), testSubscriptionName, subscriptionStateAtLatestChecker()) - Expect(err).NotTo(HaveOccurred()) - }) - }) - - When("the target catalog's sourceType", func() { - Context("is unknown", func() { - It("should surface catalog health", func() { - cs := &operatorsv1alpha1.CatalogSource{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.CatalogSourceKind, - APIVersion: operatorsv1alpha1.CatalogSourceCRDAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "cs", - }, - Spec: operatorsv1alpha1.CatalogSourceSpec{ - SourceType: "goose", - GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ - SecurityContextConfig: operatorsv1alpha1.Restricted, - }, - }, - } - - var err error - cs, err = crc.OperatorsV1alpha1().CatalogSources(generatedNamespace.GetName()).Create(context.Background(), cs, metav1.CreateOptions{}) - defer func() { - err = crc.OperatorsV1alpha1().CatalogSources(cs.GetNamespace()).Delete(context.Background(), cs.GetName(), *deleteOpts) - Expect(err).ToNot(HaveOccurred()) - }() - - subName := genName("sub-") - cleanup := createSubscriptionForCatalog( - crc, - cs.GetNamespace(), - subName, - cs.GetName(), - testPackageName, - betaChannel, - "", - operatorsv1alpha1.ApprovalManual, - ) - defer cleanup() - - var sub *operatorsv1alpha1.Subscription - sub, err = fetchSubscription( - crc, - cs.GetNamespace(), - subName, - subscriptionHasCondition( - operatorsv1alpha1.SubscriptionCatalogSourcesUnhealthy, - corev1.ConditionTrue, - operatorsv1alpha1.UnhealthyCatalogSourceFound, - fmt.Sprintf("targeted catalogsource %s/%s unhealthy", cs.GetNamespace(), cs.GetName()), - ), - ) - Expect(err).NotTo(HaveOccurred()) - Expect(sub).ToNot(BeNil()) - - By(`Get the latest CatalogSource`) - cs, err = crc.OperatorsV1alpha1().CatalogSources(cs.GetNamespace()).Get(context.Background(), cs.GetName(), getOpts) - Expect(err).NotTo(HaveOccurred()) - Expect(cs).ToNot(BeNil()) - }) - }) - - Context("is grpc and its spec is missing the address and image fields", func() { - It("should surface catalog health", func() { - By(`Create a CatalogSource pointing to the grpc pod`) - cs := &operatorsv1alpha1.CatalogSource{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.CatalogSourceKind, - APIVersion: operatorsv1alpha1.CatalogSourceCRDAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: genName("cs-"), - Namespace: generatedNamespace.GetName(), - }, - Spec: operatorsv1alpha1.CatalogSourceSpec{ - SourceType: operatorsv1alpha1.SourceTypeGrpc, - GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ - SecurityContextConfig: operatorsv1alpha1.Restricted, - }, - }, - } - - var err error - cs, err = crc.OperatorsV1alpha1().CatalogSources(generatedNamespace.GetName()).Create(context.Background(), cs, metav1.CreateOptions{}) - defer func() { - err = crc.OperatorsV1alpha1().CatalogSources(cs.GetNamespace()).Delete(context.Background(), cs.GetName(), *deleteOpts) - Expect(err).ToNot(HaveOccurred()) - }() - - By(`Wait for the CatalogSource status to be updated to reflect its invalid spec`) - _, err = fetchCatalogSourceOnStatus(crc, cs.GetName(), cs.GetNamespace(), catalogSourceInvalidSpec) - Expect(err).ToNot(HaveOccurred(), "catalog source did not become ready") - - subName := genName("sub-") - cleanup := createSubscriptionForCatalog( - crc, - cs.GetNamespace(), - subName, - cs.GetName(), - testPackageName, - betaChannel, - "", - operatorsv1alpha1.ApprovalManual, - ) - defer cleanup() - - var sub *operatorsv1alpha1.Subscription - sub, err = fetchSubscription( - crc, - cs.GetNamespace(), - subName, - subscriptionHasCondition( - operatorsv1alpha1.SubscriptionCatalogSourcesUnhealthy, - corev1.ConditionTrue, - operatorsv1alpha1.UnhealthyCatalogSourceFound, - fmt.Sprintf("targeted catalogsource %s/%s unhealthy", cs.GetNamespace(), cs.GetName()), - ), - ) - Expect(err).NotTo(HaveOccurred()) - Expect(sub).ToNot(BeNil()) - }) - }) - - Context("is internal and its spec is missing the configmap reference", func() { - It("should surface catalog health", func() { - cs := &operatorsv1alpha1.CatalogSource{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.CatalogSourceKind, - APIVersion: operatorsv1alpha1.CatalogSourceCRDAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: genName("cs-"), - Namespace: generatedNamespace.GetName(), - }, - Spec: operatorsv1alpha1.CatalogSourceSpec{ - SourceType: operatorsv1alpha1.SourceTypeInternal, - GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ - SecurityContextConfig: operatorsv1alpha1.Restricted, - }, - }, - } - - var err error - cs, err = crc.OperatorsV1alpha1().CatalogSources(generatedNamespace.GetName()).Create(context.Background(), cs, metav1.CreateOptions{}) - defer func() { - err = crc.OperatorsV1alpha1().CatalogSources(cs.GetNamespace()).Delete(context.Background(), cs.GetName(), *deleteOpts) - Expect(err).ToNot(HaveOccurred()) - }() - - subName := genName("sub-") - cleanup := createSubscriptionForCatalog( - crc, - cs.GetNamespace(), - subName, - cs.GetName(), - testPackageName, - betaChannel, - "", - operatorsv1alpha1.ApprovalManual, - ) - defer cleanup() - - var sub *operatorsv1alpha1.Subscription - sub, err = fetchSubscription( - crc, - cs.GetNamespace(), - subName, - subscriptionHasCondition( - operatorsv1alpha1.SubscriptionCatalogSourcesUnhealthy, - corev1.ConditionTrue, - operatorsv1alpha1.UnhealthyCatalogSourceFound, - fmt.Sprintf("targeted catalogsource %s/%s unhealthy", cs.GetNamespace(), cs.GetName()), - ), - ) - Expect(err).NotTo(HaveOccurred()) - Expect(sub).ToNot(BeNil()) - }) - }) - - Context("is configmap and its spec is missing the configmap reference", func() { - It("should surface catalog health", func() { - cs := &operatorsv1alpha1.CatalogSource{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.CatalogSourceKind, - APIVersion: operatorsv1alpha1.CatalogSourceCRDAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: genName("cs-"), - Namespace: generatedNamespace.GetName(), - }, - Spec: operatorsv1alpha1.CatalogSourceSpec{ - SourceType: operatorsv1alpha1.SourceTypeInternal, - GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ - SecurityContextConfig: operatorsv1alpha1.Restricted, - }, - }, - } - - var err error - cs, err = crc.OperatorsV1alpha1().CatalogSources(generatedNamespace.GetName()).Create(context.Background(), cs, metav1.CreateOptions{}) - defer func() { - err = crc.OperatorsV1alpha1().CatalogSources(cs.GetNamespace()).Delete(context.Background(), cs.GetName(), *deleteOpts) - Expect(err).ToNot(HaveOccurred()) - }() - - subName := genName("sub-") - cleanup := createSubscriptionForCatalog( - crc, - cs.GetNamespace(), - subName, - cs.GetName(), - testPackageName, - betaChannel, - "", - operatorsv1alpha1.ApprovalAutomatic, - ) - defer cleanup() - - var sub *operatorsv1alpha1.Subscription - sub, err = fetchSubscription( - crc, - cs.GetNamespace(), - subName, - subscriptionHasCondition( - operatorsv1alpha1.SubscriptionCatalogSourcesUnhealthy, - corev1.ConditionTrue, - operatorsv1alpha1.UnhealthyCatalogSourceFound, - fmt.Sprintf("targeted catalogsource %s/%s unhealthy", cs.GetNamespace(), cs.GetName()), - ), - ) - Expect(err).NotTo(HaveOccurred()) - Expect(sub).ToNot(BeNil()) - }) - }) - }) - - }) - - It("can reconcile InstallPlan status", func() { - By(`TestSubscriptionInstallPlanStatus ensures that a Subscription has the appropriate status conditions for possible referenced InstallPlan states.`) - c := newKubeClient() - crc := newCRClient() - - By(`Create CatalogSource, cs, in ns`) - pkgName := genName("pkg-") - channelName := genName("channel-") - crd := newCRD(pkgName) - csv := newCSV(pkgName, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) - manifests := []registry.PackageManifest{ - { - PackageName: pkgName, - Channels: []registry.PackageChannel{ - {Name: channelName, CurrentCSVName: csv.GetName()}, - }, - DefaultChannelName: channelName, - }, - } - catalogName := genName("catalog-") - _, cleanupCatalogSource := createInternalCatalogSource(c, crc, catalogName, generatedNamespace.GetName(), manifests, []apiextensionsv1.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{csv}) - defer cleanupCatalogSource() - _, err := fetchCatalogSourceOnStatus(crc, catalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) - Expect(err).ToNot(HaveOccurred()) - - By(`Create Subscription to a package of cs in ns, sub`) - subName := genName("sub-") - defer createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subName, catalogName, pkgName, channelName, pkgName, operatorsv1alpha1.ApprovalAutomatic)() - - By(`Wait for the package from sub to install successfully with no remaining InstallPlan status conditions`) - checker := subscriptionStateAtLatestChecker() - sub, err := fetchSubscription(crc, generatedNamespace.GetName(), subName, func(s *operatorsv1alpha1.Subscription) bool { - for _, cond := range s.Status.Conditions { - switch cond.Type { - case operatorsv1alpha1.SubscriptionInstallPlanMissing, operatorsv1alpha1.SubscriptionInstallPlanPending, operatorsv1alpha1.SubscriptionInstallPlanFailed: - return false - } - } - return checker(s) - }) - Expect(err).ToNot(HaveOccurred()) - Expect(sub).ToNot(BeNil()) - - By(`Store conditions for later comparision`) - conds := sub.Status.Conditions - - ref := sub.Status.InstallPlanRef - Expect(ref).ToNot(BeNil()) - - By(`Get the InstallPlan`) - plan := &operatorsv1alpha1.InstallPlan{} - plan.SetNamespace(ref.Namespace) - plan.SetName(ref.Name) - - By(`Set the InstallPlan's approval mode to Manual`) - Eventually(Apply(plan, func(p *operatorsv1alpha1.InstallPlan) error { - p.Spec.Approval = operatorsv1alpha1.ApprovalManual - - p.Spec.Approved = false - return nil - })).Should(Succeed()) - - By(`Set the InstallPlan's phase to None`) - Eventually(Apply(plan, func(p *operatorsv1alpha1.InstallPlan) error { - p.Status.Phase = operatorsv1alpha1.InstallPlanPhaseNone - return nil - })).Should(Succeed()) - - By(`Wait for sub to have status condition SubscriptionInstallPlanPending true and reason InstallPlanNotYetReconciled`) - sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, func(s *operatorsv1alpha1.Subscription) bool { - cond := s.Status.GetCondition(operatorsv1alpha1.SubscriptionInstallPlanPending) - return cond.Status == corev1.ConditionTrue && cond.Reason == operatorsv1alpha1.InstallPlanNotYetReconciled - }) - Expect(err).ToNot(HaveOccurred()) - - By(`Set the phase to InstallPlanPhaseRequiresApproval`) - Eventually(Apply(plan, func(p *operatorsv1alpha1.InstallPlan) error { - p.Status.Phase = operatorsv1alpha1.InstallPlanPhaseRequiresApproval - return nil - })).Should(Succeed()) - - By(`Wait for sub to have status condition SubscriptionInstallPlanPending true and reason RequiresApproval`) - sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, func(s *operatorsv1alpha1.Subscription) bool { - cond := s.Status.GetCondition(operatorsv1alpha1.SubscriptionInstallPlanPending) - return cond.Status == corev1.ConditionTrue && cond.Reason == string(operatorsv1alpha1.InstallPlanPhaseRequiresApproval) - }) - Expect(err).ToNot(HaveOccurred()) - - By(`Set the phase to InstallPlanPhaseInstalling`) - Eventually(Apply(plan, func(p *operatorsv1alpha1.InstallPlan) error { - p.Status.Phase = operatorsv1alpha1.InstallPlanPhaseInstalling - return nil - })).Should(Succeed()) - - By(`Wait for sub to have status condition SubscriptionInstallPlanPending true and reason Installing`) - sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, func(s *operatorsv1alpha1.Subscription) bool { - cond := s.Status.GetCondition(operatorsv1alpha1.SubscriptionInstallPlanPending) - isConditionPresent := cond.Status == corev1.ConditionTrue && cond.Reason == string(operatorsv1alpha1.InstallPlanPhaseInstalling) - - if isConditionPresent { - return true - } - - // Sometimes the transition from installing to complete can be so quick that the test does not capture - // the condition in the subscription before it is removed. To mitigate this, we check if the installplan - // has transitioned to complete and exit out the fetch subscription loop if so. - // This is a mitigation. We should probably fix this test appropriately. - // issue: https://github.com/operator-framework/operator-lifecycle-manager/issues/2667 - ip, err := crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Get(context.TODO(), plan.Name, metav1.GetOptions{}) - if err != nil { - // retry on failure - return false - } - isInstallPlanComplete := ip.Status.Phase == operatorsv1alpha1.InstallPlanPhaseComplete - - return isInstallPlanComplete - }) - Expect(err).ToNot(HaveOccurred()) - - By(`Set the phase to InstallPlanPhaseFailed and remove all status conditions`) - Eventually(Apply(plan, func(p *operatorsv1alpha1.InstallPlan) error { - p.Status.Phase = operatorsv1alpha1.InstallPlanPhaseFailed - p.Status.Conditions = nil - return nil - })).Should(Succeed()) - - By(`Wait for sub to have status condition SubscriptionInstallPlanFailed true and reason InstallPlanFailed`) - sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, func(s *operatorsv1alpha1.Subscription) bool { - cond := s.Status.GetCondition(operatorsv1alpha1.SubscriptionInstallPlanFailed) - return cond.Status == corev1.ConditionTrue && cond.Reason == operatorsv1alpha1.InstallPlanFailed - }) - Expect(err).ToNot(HaveOccurred()) - - By(`Set status condition of type Installed to false with reason InstallComponentFailed`) - Eventually(Apply(plan, func(p *operatorsv1alpha1.InstallPlan) error { - p.Status.Phase = operatorsv1alpha1.InstallPlanPhaseFailed - failedCond := p.Status.GetCondition(operatorsv1alpha1.InstallPlanInstalled) - failedCond.Status = corev1.ConditionFalse - failedCond.Reason = operatorsv1alpha1.InstallPlanReasonComponentFailed - p.Status.SetCondition(failedCond) - return nil - })).Should(Succeed()) - - By(`Wait for sub to have status condition SubscriptionInstallPlanFailed true and reason InstallComponentFailed`) - sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, func(s *operatorsv1alpha1.Subscription) bool { - cond := s.Status.GetCondition(operatorsv1alpha1.SubscriptionInstallPlanFailed) - return cond.Status == corev1.ConditionTrue && cond.Reason == string(operatorsv1alpha1.InstallPlanReasonComponentFailed) - }) - Expect(err).ToNot(HaveOccurred()) - - By(`Delete the referenced InstallPlan`) - Eventually(func() error { - return crc.OperatorsV1alpha1().InstallPlans(ref.Namespace).Delete(context.Background(), ref.Name, metav1.DeleteOptions{}) - }).Should(Succeed()) - - By(`Wait for sub to have status condition SubscriptionInstallPlanMissing true`) - sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, func(s *operatorsv1alpha1.Subscription) bool { - return s.Status.GetCondition(operatorsv1alpha1.SubscriptionInstallPlanMissing).Status == corev1.ConditionTrue - }) - Expect(err).ToNot(HaveOccurred()) - Expect(sub).ToNot(BeNil()) - - By(`Ensure InstallPlan-related status conditions match what we're expecting`) - for _, cond := range conds { - switch condType := cond.Type; condType { - case operatorsv1alpha1.SubscriptionInstallPlanPending, operatorsv1alpha1.SubscriptionInstallPlanFailed: - require.FailNowf(GinkgoT(), "failed", "subscription contains unexpected installplan condition: %v", cond) - case operatorsv1alpha1.SubscriptionInstallPlanMissing: - require.Equal(GinkgoT(), operatorsv1alpha1.ReferencedInstallPlanNotFound, cond.Reason) - } - } - }) - - It("creation with pod config", func() { - - newConfigClient := func(t GinkgoTInterface) configv1client.ConfigV1Interface { - client, err := configv1client.NewForConfig(ctx.Ctx().RESTConfig()) - require.NoError(GinkgoT(), err) - - return client - } - - proxyEnvVarFunc := func(t GinkgoTInterface, client configv1client.ConfigV1Interface) []corev1.EnvVar { - if discovery.ServerSupportsVersion(ctx.Ctx().KubeClient().KubernetesInterface().Discovery(), configv1.GroupVersion) != nil { - return nil - } - - proxy, getErr := client.Proxies().Get(context.Background(), "cluster", metav1.GetOptions{}) - if apierrors.IsNotFound(getErr) { - return nil - } - require.NoError(GinkgoT(), getErr) - require.NotNil(GinkgoT(), proxy) - - proxyEnv := []corev1.EnvVar{} - - if proxy.Status.HTTPProxy != "" { - proxyEnv = append(proxyEnv, corev1.EnvVar{ - Name: "HTTP_PROXY", - Value: proxy.Status.HTTPProxy, - }) - } - - if proxy.Status.HTTPSProxy != "" { - proxyEnv = append(proxyEnv, corev1.EnvVar{ - Name: "HTTPS_PROXY", - Value: proxy.Status.HTTPSProxy, - }) - } - - if proxy.Status.NoProxy != "" { - proxyEnv = append(proxyEnv, corev1.EnvVar{ - Name: "NO_PROXY", - Value: proxy.Status.NoProxy, - }) - } - - return proxyEnv - } - - kubeClient := newKubeClient() - crClient := newCRClient() - config := newConfigClient(GinkgoT()) - - By(`Create a ConfigMap that is mounted to the operator via the subscription`) - testConfigMapName := genName("test-configmap-") - testConfigMap := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: testConfigMapName, - }, - } - - _, err := kubeClient.KubernetesInterface().CoreV1().ConfigMaps(generatedNamespace.GetName()).Create(context.Background(), testConfigMap, metav1.CreateOptions{}) - require.NoError(GinkgoT(), err) - defer func() { - err := kubeClient.KubernetesInterface().CoreV1().ConfigMaps(generatedNamespace.GetName()).Delete(context.Background(), testConfigMap.Name, metav1.DeleteOptions{}) - require.NoError(GinkgoT(), err) - }() - - By(`Configure the Subscription.`) - - podEnv := []corev1.EnvVar{ - { - Name: "MY_ENV_VARIABLE1", - Value: "value1", - }, - { - Name: "MY_ENV_VARIABLE2", - Value: "value2", - }, - } - testVolumeName := genName("test-volume-") - podVolumes := []corev1.Volume{ - { - Name: testVolumeName, - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: testConfigMapName, - }, - }, - }, - }, - } - - podVolumeMounts := []corev1.VolumeMount{ - {Name: testVolumeName, MountPath: "/test"}, - } - - podTolerations := []corev1.Toleration{ - { - Key: "my-toleration-key", - Value: "my-toleration-value", - Effect: corev1.TaintEffectNoSchedule, - Operator: corev1.TolerationOpEqual, - }, - } - podResources := &corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("100m"), - }, - Requests: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("100m"), - corev1.ResourceMemory: resource.MustParse("128Mi"), - }, - } - - podConfig := &operatorsv1alpha1.SubscriptionConfig{ - Env: podEnv, - Volumes: podVolumes, - VolumeMounts: podVolumeMounts, - Tolerations: podTolerations, - Resources: podResources, - } - - permissions := deploymentPermissions() - catsrc, subSpec, catsrcCleanup := newCatalogSource(GinkgoT(), kubeClient, crClient, "podconfig", generatedNamespace.GetName(), permissions) - defer catsrcCleanup() - - By(`Ensure that the catalog source is resolved before we create a subscription.`) - _, err = fetchCatalogSourceOnStatus(crClient, catsrc.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) - require.NoError(GinkgoT(), err) - - subscriptionName := genName("podconfig-sub-") - subSpec.Config = podConfig - cleanupSubscription := createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subSpec) - defer cleanupSubscription() - - subscription, err := fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionStateAtLatestChecker()) - require.NoError(GinkgoT(), err) - require.NotNil(GinkgoT(), subscription) - - proxyEnv := proxyEnvVarFunc(GinkgoT(), config) - expected := podEnv - expected = append(expected, proxyEnv...) - - Eventually(func() error { - csv, err := fetchCSV(crClient, generatedNamespace.GetName(), subscription.Status.CurrentCSV, buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded)) - if err != nil { - return err - } - - return checkDeploymentWithPodConfiguration(kubeClient, csv, podConfig.Env, podConfig.Volumes, podConfig.VolumeMounts, podConfig.Tolerations, podConfig.Resources) - }).Should(Succeed()) - }) - - It("creation with nodeSelector config", func() { - kubeClient := newKubeClient() - crClient := newCRClient() - - By(`Create a ConfigMap that is mounted to the operator via the subscription`) - testConfigMapName := genName("test-configmap-") - testConfigMap := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: testConfigMapName, - }, - } - - _, err := kubeClient.KubernetesInterface().CoreV1().ConfigMaps(generatedNamespace.GetName()).Create(context.Background(), testConfigMap, metav1.CreateOptions{}) - require.NoError(GinkgoT(), err) - defer func() { - err := kubeClient.KubernetesInterface().CoreV1().ConfigMaps(generatedNamespace.GetName()).Delete(context.Background(), testConfigMap.Name, metav1.DeleteOptions{}) - require.NoError(GinkgoT(), err) - }() - - By(`Configure the Subscription.`) - podNodeSelector := map[string]string{ - "foo": "bar", - } - - podConfig := &operatorsv1alpha1.SubscriptionConfig{ - NodeSelector: podNodeSelector, - } - - permissions := deploymentPermissions() - catsrc, subSpec, catsrcCleanup := newCatalogSource(GinkgoT(), kubeClient, crClient, "podconfig", generatedNamespace.GetName(), permissions) - defer catsrcCleanup() - - By(`Ensure that the catalog source is resolved before we create a subscription.`) - _, err = fetchCatalogSourceOnStatus(crClient, catsrc.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) - require.NoError(GinkgoT(), err) - - subscriptionName := genName("podconfig-sub-") - subSpec.Config = podConfig - cleanupSubscription := createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subSpec) - defer cleanupSubscription() - - subscription, err := fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionStateAtLatestChecker()) - require.NoError(GinkgoT(), err) - require.NotNil(GinkgoT(), subscription) - - csv, err := fetchCSV(crClient, generatedNamespace.GetName(), subscription.Status.CurrentCSV, buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseInstalling, operatorsv1alpha1.CSVPhaseSucceeded)) - require.NoError(GinkgoT(), err) - - Eventually(func() error { - return checkDeploymentHasPodConfigNodeSelector(GinkgoT(), kubeClient, csv, podNodeSelector) - }, timeout, interval).Should(Succeed()) - - }) - - It("[FLAKE] creation with dependencies", func() { - - kubeClient := newKubeClient() - crClient := newCRClient() - - permissions := deploymentPermissions() - - catsrc, subSpec, catsrcCleanup := newCatalogSourceWithDependencies(GinkgoT(), kubeClient, crClient, "podconfig", generatedNamespace.GetName(), permissions) - defer catsrcCleanup() - - By(`Ensure that the catalog source is resolved before we create a subscription.`) - _, err := fetchCatalogSourceOnStatus(crClient, catsrc.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) - require.NoError(GinkgoT(), err) - - By(`Create duplicates of the CatalogSource`) - for i := 0; i < 10; i++ { - duplicateCatsrc, _, duplicateCatSrcCleanup := newCatalogSourceWithDependencies(GinkgoT(), kubeClient, crClient, "podconfig", generatedNamespace.GetName(), permissions) - defer duplicateCatSrcCleanup() - - By(`Ensure that the catalog source is resolved before we create a subscription.`) - _, err = fetchCatalogSourceOnStatus(crClient, duplicateCatsrc.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) - require.NoError(GinkgoT(), err) - } - - By(`Create a subscription that has a dependency`) - subscriptionName := genName("podconfig-sub-") - cleanupSubscription := createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subSpec) - defer cleanupSubscription() - - subscription, err := fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionStateAtLatestChecker()) - require.NoError(GinkgoT(), err) - require.NotNil(GinkgoT(), subscription) - - By(`Check that a single catalog source was used to resolve the InstallPlan`) - installPlan, err := fetchInstallPlanWithNamespace(GinkgoT(), crClient, subscription.Status.InstallPlanRef.Name, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete)) - require.NoError(GinkgoT(), err) - require.Len(GinkgoT(), installPlan.Status.CatalogSources, 1) - }) - - It("creation with dependencies required and provided in different versions of an operator in the same package", func() { - By(` PARITY: this test covers the same scenario as the TestSolveOperators_PackageCannotSelfSatisfy unit test`) - kubeClient := ctx.Ctx().KubeClient() - crClient := ctx.Ctx().OperatorClient() - - crd := newCRD(genName("ins")) - crd2 := newCRD(genName("ins")) - - By(`csvs for catalogsource 1`) - csvs1 := make([]operatorsv1alpha1.ClusterServiceVersion, 0) - - By(`csvs for catalogsource 2`) - csvs2 := make([]operatorsv1alpha1.ClusterServiceVersion, 0) - - testPackage := registry.PackageManifest{PackageName: "test-package"} - By("Package A", func() { - Step(1, "Default Channel: Stable", func() { - testPackage.DefaultChannelName = stableChannel - }) - - Step(1, "Channel Stable", func() { - Step(2, "Operator A (Requires CRD, CRD 2)", func() { - csvA := newCSV("csv-a", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), nil, []apiextensionsv1.CustomResourceDefinition{crd, crd2}, nil) - testPackage. - Channels = append(testPackage. - Channels, registry.PackageChannel{Name: stableChannel, CurrentCSVName: csvA.GetName()}) - csvs1 = append(csvs1, csvA) - }) - }) - - Step(1, "Channel Alpha", func() { - Step(2, "Operator ABC (Provides: CRD, CRD 2)", func() { - csvABC := newCSV("csv-abc", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd, crd2}, nil, nil) - testPackage. - Channels = append(testPackage. - Channels, registry.PackageChannel{Name: alphaChannel, CurrentCSVName: csvABC.GetName()}) - csvs1 = append(csvs1, csvABC) - }) - }) - }) - - anotherPackage := registry.PackageManifest{PackageName: "another-package"} - By("Package B", func() { - Step(1, "Default Channel: Stable", func() { - anotherPackage.DefaultChannelName = stableChannel - }) - - Step(1, "Channel Stable", func() { - Step(2, "Operator B (Provides: CRD)", func() { - csvB := newCSV("csv-b", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) - anotherPackage.Channels = append(anotherPackage.Channels, registry.PackageChannel{Name: stableChannel, CurrentCSVName: csvB.GetName()}) - csvs1 = append(csvs1, csvB) - }) - }) - - Step(1, "Channel Alpha", func() { - Step(2, "Operator D (Provides: CRD)", func() { - csvD := newCSV("csv-d", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) - anotherPackage.Channels = append(anotherPackage.Channels, registry.PackageChannel{Name: alphaChannel, CurrentCSVName: csvD.GetName()}) - csvs1 = append(csvs1, csvD) - }) - }) - }) - - packageBInCatsrc2 := registry.PackageManifest{PackageName: "another-package"} - By("Package B", func() { - Step(1, "Default Channel: Stable", func() { - packageBInCatsrc2.DefaultChannelName = stableChannel - }) - - Step(1, "Channel Stable", func() { - Step(2, "Operator C (Provides: CRD 2)", func() { - csvC := newCSV("csv-c", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd2}, nil, nil) - packageBInCatsrc2.Channels = append(packageBInCatsrc2.Channels, registry.PackageChannel{Name: stableChannel, CurrentCSVName: csvC.GetName()}) - csvs2 = append(csvs2, csvC) - }) - }) - }) - - packageC := registry.PackageManifest{PackageName: "PackageC"} - By("Package C", func() { - Step(1, "Default Channel: Stable", func() { - packageC.DefaultChannelName = stableChannel - }) - - Step(1, "Channel Stable", func() { - Step(2, "Operator E (Provides: CRD 2)", func() { - csvE := newCSV("csv-e", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd2}, nil, nil) - packageC.Channels = append(packageC.Channels, registry.PackageChannel{Name: stable, CurrentCSVName: csvE.GetName()}) - csvs2 = append(csvs2, csvE) - }) - }) - }) - - By(`create catalogsources`) - var catsrc, catsrc2 *operatorsv1alpha1.CatalogSource - var cleanup cleanupFunc - By("creating catalogsources", func() { - var c1, c2 cleanupFunc - catsrc, c1 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc"), generatedNamespace.GetName(), []registry.PackageManifest{testPackage, anotherPackage}, []apiextensionsv1.CustomResourceDefinition{crd, crd2}, csvs1) - catsrc2, c2 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc2"), generatedNamespace.GetName(), []registry.PackageManifest{packageBInCatsrc2, packageC}, []apiextensionsv1.CustomResourceDefinition{crd, crd2}, csvs2) - cleanup = func() { - c1() - c2() - } - }) - defer cleanup() - - By("waiting for catalogsources to be ready", func() { - _, err := fetchCatalogSourceOnStatus(crClient, catsrc.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) - require.NoError(GinkgoT(), err) - _, err = fetchCatalogSourceOnStatus(crClient, catsrc2.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) - require.NoError(GinkgoT(), err) - }) - - By(`Create a subscription for test-package in catsrc`) - subscriptionSpec := &operatorsv1alpha1.SubscriptionSpec{ - CatalogSource: catsrc.GetName(), - CatalogSourceNamespace: catsrc.GetNamespace(), - Package: testPackage.PackageName, - Channel: stableChannel, - InstallPlanApproval: operatorsv1alpha1.ApprovalAutomatic, - } - subscriptionName := genName("sub-") - cleanupSubscription := createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subscriptionSpec) - defer cleanupSubscription() - - subscription, err := fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) - require.NoError(GinkgoT(), err) - require.NotNil(GinkgoT(), subscription) - - By(`ensure correct CSVs were picked`) - var got []string - Eventually(func() []string { - ip, err := crClient.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Get(context.Background(), subscription.Status.InstallPlanRef.Name, metav1.GetOptions{}) - if err != nil { - return nil - } - got = ip.Spec.ClusterServiceVersionNames - return got - }).ShouldNot(BeNil()) - require.ElementsMatch(GinkgoT(), []string{"csv-a", "csv-b", "csv-e"}, got) - }) - - Context("to an operator with dependencies from different CatalogSources with priorities", func() { - var ( - kubeClient operatorclient.ClientInterface - crClient versioned.Interface - crd apiextensionsv1.CustomResourceDefinition - packageMain, packageDepRight, packageDepWrong registry.PackageManifest - csvsMain, csvsRight, csvsWrong []operatorsv1alpha1.ClusterServiceVersion - catsrcMain, catsrcDepRight, catsrcDepWrong *operatorsv1alpha1.CatalogSource - cleanup, cleanupSubscription cleanupFunc - ) - const ( - mainCSVName = "csv-main" - rightCSVName = "csv-right" - wrongCSVName = "csv-wrong" - ) - - BeforeEach(func() { - kubeClient = ctx.Ctx().KubeClient() - crClient = ctx.Ctx().OperatorClient() - crd = newCRD(genName("ins")) - - packageMain = registry.PackageManifest{PackageName: genName("PkgMain-")} - csv := newCSV(mainCSVName, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), nil, - []apiextensionsv1.CustomResourceDefinition{crd}, nil) - packageMain.DefaultChannelName = stableChannel - packageMain.Channels = append(packageMain.Channels, registry.PackageChannel{Name: stableChannel, CurrentCSVName: csv.GetName()}) - - csvsMain = []operatorsv1alpha1.ClusterServiceVersion{csv} - csvsRight = []operatorsv1alpha1.ClusterServiceVersion{} - csvsWrong = []operatorsv1alpha1.ClusterServiceVersion{} - }) - - Context("creating CatalogSources providing the same dependency with different names", func() { - var catsrcCleanup1, catsrcCleanup2, catsrcCleanup3 cleanupFunc - - BeforeEach(func() { - packageDepRight = registry.PackageManifest{PackageName: "PackageDependent"} - csv := newCSV(rightCSVName, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), - []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) - packageDepRight.DefaultChannelName = alphaChannel - packageDepRight.Channels = []registry.PackageChannel{{Name: alphaChannel, CurrentCSVName: csv.GetName()}} - csvsRight = append(csvsRight, csv) - - csv.Name = wrongCSVName - packageDepWrong = packageDepRight - packageDepWrong.Channels = []registry.PackageChannel{{Name: alphaChannel, CurrentCSVName: csv.GetName()}} - csvsWrong = append(csvsWrong, csv) - - catsrcMain, catsrcCleanup1 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc"), generatedNamespace.GetName(), - []registry.PackageManifest{packageMain}, nil, csvsMain) - - catsrcDepRight, catsrcCleanup2 = createInternalCatalogSource(kubeClient, crClient, "catsrc1", generatedNamespace.GetName(), - []registry.PackageManifest{packageDepRight}, []apiextensionsv1.CustomResourceDefinition{crd}, csvsRight) - - catsrcDepWrong, catsrcCleanup3 = createInternalCatalogSource(kubeClient, crClient, "catsrc2", generatedNamespace.GetName(), - []registry.PackageManifest{packageDepWrong}, []apiextensionsv1.CustomResourceDefinition{crd}, csvsWrong) - - _, err := fetchCatalogSourceOnStatus(crClient, catsrcMain.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) - Expect(err).ToNot(HaveOccurred()) - _, err = fetchCatalogSourceOnStatus(crClient, catsrcDepRight.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) - Expect(err).ToNot(HaveOccurred()) - _, err = fetchCatalogSourceOnStatus(crClient, catsrcDepWrong.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) - Expect(err).ToNot(HaveOccurred()) - }) - - AfterEach(func() { - if catsrcCleanup1 != nil { - catsrcCleanup1() - } - if catsrcCleanup2 != nil { - catsrcCleanup2() - } - if catsrcCleanup3 != nil { - catsrcCleanup3() - } - }) - - When("creating subscription for the main package", func() { - var subscription *operatorsv1alpha1.Subscription - - BeforeEach(func() { - By(`Create a subscription for test-package in catsrc`) - subscriptionSpec := &operatorsv1alpha1.SubscriptionSpec{ - CatalogSource: catsrcMain.GetName(), - CatalogSourceNamespace: catsrcMain.GetNamespace(), - Package: packageMain.PackageName, - Channel: stableChannel, - InstallPlanApproval: operatorsv1alpha1.ApprovalAutomatic, - } - subscriptionName := genName("sub-") - cleanupSubscription = createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subscriptionSpec) - - var err error - subscription, err = fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) - Expect(err).ToNot(HaveOccurred()) - Expect(subscription).ToNot(BeNil()) - - _, err = fetchCSV(crClient, generatedNamespace.GetName(), mainCSVName, csvSucceededChecker) - Expect(err).ToNot(HaveOccurred()) - - }) - - AfterEach(func() { - if cleanupSubscription != nil { - cleanupSubscription() - } - if cleanup != nil { - cleanup() - } - }) - - It("[FLAKE] choose the dependency from the right CatalogSource based on lexicographical name ordering of catalogs", func() { - By(`ensure correct CSVs were picked`) - Eventually(func() ([]string, error) { - ip, err := crClient.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Get(context.Background(), subscription.Status.InstallPlanRef.Name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - return ip.Spec.ClusterServiceVersionNames, nil - }).Should(ConsistOf(mainCSVName, rightCSVName)) - }) - }) - }) - - Context("creating the main and an arbitrary CatalogSources providing the same dependency", func() { - var catsrcCleanup1, catsrcCleanup2 cleanupFunc - - BeforeEach(func() { - - packageDepRight = registry.PackageManifest{PackageName: "PackageDependent"} - csv := newCSV(rightCSVName, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), - []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) - packageDepRight.DefaultChannelName = alphaChannel - packageDepRight.Channels = []registry.PackageChannel{{Name: alphaChannel, CurrentCSVName: csv.GetName()}} - csvsMain = append(csvsMain, csv) - - csv.Name = wrongCSVName - packageDepWrong = packageDepRight - packageDepWrong.Channels = []registry.PackageChannel{{Name: alphaChannel, CurrentCSVName: csv.GetName()}} - csvsWrong = append(csvsWrong, csv) - - catsrcMain, catsrcCleanup1 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc"), generatedNamespace.GetName(), - []registry.PackageManifest{packageDepRight, packageMain}, []apiextensionsv1.CustomResourceDefinition{crd}, csvsMain) - - catsrcDepWrong, catsrcCleanup2 = createInternalCatalogSourceWithPriority(kubeClient, crClient, - genName("catsrc"), generatedNamespace.GetName(), []registry.PackageManifest{packageDepWrong}, []apiextensionsv1.CustomResourceDefinition{crd}, - csvsWrong, 100) - - By(`waiting for catalogsources to be ready`) - _, err := fetchCatalogSourceOnStatus(crClient, catsrcMain.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) - Expect(err).ToNot(HaveOccurred()) - _, err = fetchCatalogSourceOnStatus(crClient, catsrcDepWrong.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) - Expect(err).ToNot(HaveOccurred()) - }) - - AfterEach(func() { - if catsrcCleanup1 != nil { - catsrcCleanup1() - } - if catsrcCleanup2 != nil { - catsrcCleanup2() - } - - }) - - When("creating subscription for the main package", func() { - var subscription *operatorsv1alpha1.Subscription - - BeforeEach(func() { - By(`Create a subscription for test-package in catsrc`) - subscriptionSpec := &operatorsv1alpha1.SubscriptionSpec{ - CatalogSource: catsrcMain.GetName(), - CatalogSourceNamespace: catsrcMain.GetNamespace(), - Package: packageMain.PackageName, - Channel: stableChannel, - InstallPlanApproval: operatorsv1alpha1.ApprovalAutomatic, - } - subscriptionName := genName("sub-") - cleanupSubscription = createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subscriptionSpec) - - var err error - subscription, err = fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) - Expect(err).ToNot(HaveOccurred()) - Expect(subscription).ToNot(BeNil()) - - _, err = fetchCSV(crClient, generatedNamespace.GetName(), mainCSVName, csvSucceededChecker) - Expect(err).ToNot(HaveOccurred()) - - }) - - AfterEach(func() { - if cleanupSubscription != nil { - cleanupSubscription() - } - if cleanup != nil { - cleanup() - } - }) - - It("choose the dependent package from the same catsrc as the installing operator", func() { - By(`ensure correct CSVs were picked`) - Eventually(func() ([]string, error) { - ip, err := crClient.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Get(context.Background(), subscription.Status.InstallPlanRef.Name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - return ip.Spec.ClusterServiceVersionNames, nil - }).Should(ConsistOf(mainCSVName, rightCSVName)) - }) - }) - }) - - Context("creating CatalogSources providing the same dependency with different priority value", func() { - var catsrcCleanup1, catsrcCleanup2, catsrcCleanup3 cleanupFunc - - BeforeEach(func() { - packageDepRight = registry.PackageManifest{PackageName: "PackageDependent"} - csv := newCSV(rightCSVName, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), - []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) - packageDepRight.DefaultChannelName = alphaChannel - packageDepRight.Channels = []registry.PackageChannel{{Name: alphaChannel, CurrentCSVName: csv.GetName()}} - csvsRight = append(csvsRight, csv) - - csv.Name = wrongCSVName - packageDepWrong = packageDepRight - packageDepWrong.Channels = []registry.PackageChannel{{Name: alphaChannel, CurrentCSVName: csv.GetName()}} - csvsWrong = append(csvsWrong, csv) - - catsrcMain, catsrcCleanup1 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc"), generatedNamespace.GetName(), - []registry.PackageManifest{packageMain}, nil, csvsMain) - - catsrcDepRight, catsrcCleanup2 = createInternalCatalogSourceWithPriority(kubeClient, crClient, - genName("catsrc"), generatedNamespace.GetName(), []registry.PackageManifest{packageDepRight}, []apiextensionsv1.CustomResourceDefinition{crd}, - csvsRight, 100) - - catsrcDepWrong, catsrcCleanup3 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc"), generatedNamespace.GetName(), - []registry.PackageManifest{packageDepWrong}, []apiextensionsv1.CustomResourceDefinition{crd}, csvsWrong) - - By(`waiting for catalogsources to be ready`) - _, err := fetchCatalogSourceOnStatus(crClient, catsrcMain.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) - Expect(err).ToNot(HaveOccurred()) - _, err = fetchCatalogSourceOnStatus(crClient, catsrcDepRight.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) - Expect(err).ToNot(HaveOccurred()) - _, err = fetchCatalogSourceOnStatus(crClient, catsrcDepWrong.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) - Expect(err).ToNot(HaveOccurred()) - }) - - AfterEach(func() { - if catsrcCleanup1 != nil { - catsrcCleanup1() - } - if catsrcCleanup2 != nil { - catsrcCleanup2() - } - if catsrcCleanup3 != nil { - catsrcCleanup3() - } - }) - - When("creating subscription for the main package", func() { - var subscription *operatorsv1alpha1.Subscription - - BeforeEach(func() { - By(`Create a subscription for test-package in catsrc`) - subscriptionSpec := &operatorsv1alpha1.SubscriptionSpec{ - CatalogSource: catsrcMain.GetName(), - CatalogSourceNamespace: catsrcMain.GetNamespace(), - Package: packageMain.PackageName, - Channel: stableChannel, - InstallPlanApproval: operatorsv1alpha1.ApprovalAutomatic, - } - subscriptionName := genName("sub-") - cleanupSubscription = createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subscriptionSpec) - - var err error - subscription, err = fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) - Expect(err).ToNot(HaveOccurred()) - Expect(subscription).ToNot(BeNil()) - - _, err = fetchCSV(crClient, generatedNamespace.GetName(), mainCSVName, csvSucceededChecker) - Expect(err).ToNot(HaveOccurred()) - - }) - - AfterEach(func() { - if cleanupSubscription != nil { - cleanupSubscription() - } - if cleanup != nil { - cleanup() - } - }) - - It("choose the dependent package from the catsrc with higher priority", func() { - By(`ensure correct CSVs were picked`) - Eventually(func() ([]string, error) { - ip, err := crClient.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Get(context.Background(), subscription.Status.InstallPlanRef.Name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - return ip.Spec.ClusterServiceVersionNames, nil - }).Should(ConsistOf(mainCSVName, rightCSVName)) - }) - }) - }) - - Context("creating CatalogSources providing the same dependency under test and global namespaces", func() { - var catsrcCleanup1, catsrcCleanup2, catsrcCleanup3 cleanupFunc - - BeforeEach(func() { - - packageDepRight = registry.PackageManifest{PackageName: "PackageDependent"} - csv := newCSV(rightCSVName, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), - []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) - packageDepRight.DefaultChannelName = alphaChannel - packageDepRight.Channels = []registry.PackageChannel{{Name: alphaChannel, CurrentCSVName: csv.GetName()}} - csvsRight = append(csvsRight, csv) - - csv.Name = wrongCSVName - packageDepWrong = packageDepRight - packageDepWrong.Channels = []registry.PackageChannel{{Name: alphaChannel, CurrentCSVName: csv.GetName()}} - csvsWrong = append(csvsWrong, csv) - - catsrcMain, catsrcCleanup1 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc"), generatedNamespace.GetName(), - []registry.PackageManifest{packageMain}, nil, csvsMain) - - catsrcDepRight, catsrcCleanup2 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc"), generatedNamespace.GetName(), - []registry.PackageManifest{packageDepRight}, []apiextensionsv1.CustomResourceDefinition{crd}, csvsRight) - - catsrcDepWrong, catsrcCleanup3 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc"), operatorNamespace, - []registry.PackageManifest{packageDepWrong}, []apiextensionsv1.CustomResourceDefinition{crd}, csvsWrong) - - By(`waiting for catalogsources to be ready`) - _, err := fetchCatalogSourceOnStatus(crClient, catsrcMain.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) - Expect(err).ToNot(HaveOccurred()) - _, err = fetchCatalogSourceOnStatus(crClient, catsrcDepRight.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) - Expect(err).ToNot(HaveOccurred()) - _, err = fetchCatalogSourceOnStatus(crClient, catsrcDepWrong.GetName(), operatorNamespace, catalogSourceRegistryPodSynced()) - Expect(err).ToNot(HaveOccurred()) - }) - - AfterEach(func() { - if catsrcCleanup1 != nil { - catsrcCleanup1() - } - if catsrcCleanup2 != nil { - catsrcCleanup2() - } - if catsrcCleanup3 != nil { - catsrcCleanup3() - } - }) - - When("creating subscription for the main package", func() { - var subscription *operatorsv1alpha1.Subscription - - BeforeEach(func() { - By(`Create a subscription for test-package in catsrc`) - subscriptionSpec := &operatorsv1alpha1.SubscriptionSpec{ - CatalogSource: catsrcMain.GetName(), - CatalogSourceNamespace: catsrcMain.GetNamespace(), - Package: packageMain.PackageName, - Channel: stableChannel, - InstallPlanApproval: operatorsv1alpha1.ApprovalAutomatic, - } - subscriptionName := genName("sub-") - cleanupSubscription = createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subscriptionSpec) - - var err error - subscription, err = fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) - Expect(err).ToNot(HaveOccurred()) - Expect(subscription).ToNot(BeNil()) - - _, err = fetchCSV(crClient, generatedNamespace.GetName(), mainCSVName, csvSucceededChecker) - Expect(err).ToNot(HaveOccurred()) - - }) - - AfterEach(func() { - if cleanupSubscription != nil { - cleanupSubscription() - } - if cleanup != nil { - cleanup() - } - }) - - It("choose the dependent package from the catsrc in the same namespace as the installing operator", func() { - By(`ensure correct CSVs were picked`) - Eventually(func() ([]string, error) { - ip, err := crClient.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Get(context.Background(), subscription.Status.InstallPlanRef.Name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - return ip.Spec.ClusterServiceVersionNames, nil - }).Should(ConsistOf(mainCSVName, rightCSVName)) - }) - }) - }) - }) - - It("creation in case of transferring providedAPIs", func() { - By(`csvA owns CRD1 & csvB owns CRD2 and requires CRD1`) - By(`Create subscription for csvB lead to installation of csvB and csvA`) - By(`Update catsrc to upgrade csvA to csvNewA which now requires CRD1`) - By(`csvNewA can't be installed due to no other operators provide CRD1 for it`) - By(`(Note: OLM can't pick csvA as dependency for csvNewA as it is from the same`) - By(`same package)`) - By(`Update catsrc again to upgrade csvB to csvNewB which now owns both CRD1 and`) - By(`CRD2.`) - By(`Now csvNewA and csvNewB are installed successfully as csvNewB provides CRD1`) - By(`that csvNewA requires`) - By(` PARITY: this test covers the same scenario as the TestSolveOperators_TransferApiOwnership unit test`) - kubeClient := ctx.Ctx().KubeClient() - crClient := ctx.Ctx().OperatorClient() - - crd := newCRD(genName("ins")) - crd2 := newCRD(genName("ins")) - - By(`Create CSV`) - packageName1 := genName("apackage") - packageName2 := genName("bpackage") - - By(`csvA provides CRD`) - csvA := newCSV("nginx-a", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) - By(`csvB provides CRD2 and requires CRD`) - csvB := newCSV("nginx-b", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd2}, []apiextensionsv1.CustomResourceDefinition{crd}, nil) - By(`New csvA requires CRD (transfer CRD ownership to the new csvB)`) - csvNewA := newCSV("nginx-new-a", generatedNamespace.GetName(), "nginx-a", semver.MustParse("0.2.0"), nil, []apiextensionsv1.CustomResourceDefinition{crd}, nil) - By(`New csvB provides CRD and CRD2`) - csvNewB := newCSV("nginx-new-b", generatedNamespace.GetName(), "nginx-b", semver.MustParse("0.2.0"), []apiextensionsv1.CustomResourceDefinition{crd, crd2}, nil, nil) - - By(`constraints not satisfiable:`) - By(`apackagert6cq requires at least one of catsrcc6xgr/operators/stable/nginx-new-a,`) - By(`apackagert6cq is mandatory,`) - By(`pkgunique/apackagert6cq permits at most 1 of catsrcc6xgr/operators/stable/nginx-new-a, catsrcc6xgr/operators/stable/nginx-a,`) - By(`catsrcc6xgr/operators/stable/nginx-new-a requires at least one of catsrcc6xgr/operators/stable/nginx-a`) - - By(`Create PackageManifests 1`) - By(`Contain csvA, ABC and B`) - manifests := []registry.PackageManifest{ - { - PackageName: packageName1, - Channels: []registry.PackageChannel{ - {Name: stableChannel, CurrentCSVName: csvA.GetName()}, - }, - DefaultChannelName: stableChannel, - }, - { - PackageName: packageName2, - Channels: []registry.PackageChannel{ - {Name: stableChannel, CurrentCSVName: csvB.GetName()}, - }, - DefaultChannelName: stableChannel, - }, - } - - catalogSourceName := genName("catsrc") - catsrc, cleanup := createInternalCatalogSource(kubeClient, crClient, catalogSourceName, generatedNamespace.GetName(), manifests, []apiextensionsv1.CustomResourceDefinition{crd, crd2}, []operatorsv1alpha1.ClusterServiceVersion{csvA, csvB}) - defer cleanup() - - By(`Ensure that the catalog source is resolved before we create a subscription.`) - _, err := fetchCatalogSourceOnStatus(crClient, catsrc.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) - require.NoError(GinkgoT(), err) - - subscriptionSpec := &operatorsv1alpha1.SubscriptionSpec{ - CatalogSource: catsrc.GetName(), - CatalogSourceNamespace: catsrc.GetNamespace(), - Package: packageName2, - Channel: stableChannel, - StartingCSV: csvB.GetName(), - InstallPlanApproval: operatorsv1alpha1.ApprovalAutomatic, - } - - By(`Create a subscription that has a dependency`) - subscriptionName := genName("sub-") - cleanupSubscription := createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subscriptionSpec) - defer cleanupSubscription() - - subscription, err := fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionStateAtLatestChecker()) - require.NoError(GinkgoT(), err) - require.NotNil(GinkgoT(), subscription) - - By(`Check that a single catalog source was used to resolve the InstallPlan`) - _, err = fetchInstallPlanWithNamespace(GinkgoT(), crClient, subscription.Status.InstallPlanRef.Name, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete)) - require.NoError(GinkgoT(), err) - By(`Fetch CSVs A and B`) - _, err = fetchCSV(crClient, generatedNamespace.GetName(), csvA.Name, csvSucceededChecker) - require.NoError(GinkgoT(), err) - _, err = fetchCSV(crClient, generatedNamespace.GetName(), csvB.Name, csvSucceededChecker) - require.NoError(GinkgoT(), err) - - By(`Update PackageManifest`) - manifests = []registry.PackageManifest{ - { - PackageName: packageName1, - Channels: []registry.PackageChannel{ - {Name: stableChannel, CurrentCSVName: csvNewA.GetName()}, - }, - DefaultChannelName: stableChannel, - }, - { - PackageName: packageName2, - Channels: []registry.PackageChannel{ - {Name: stableChannel, CurrentCSVName: csvB.GetName()}, - }, - DefaultChannelName: stableChannel, - }, - } - updateInternalCatalog(GinkgoT(), kubeClient, crClient, catalogSourceName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{crd, crd2}, []operatorsv1alpha1.ClusterServiceVersion{csvNewA, csvA, csvB}, manifests) - csvAsub := strings.Join([]string{packageName1, stableChannel, catalogSourceName, generatedNamespace.GetName()}, "-") - _, err = fetchSubscription(crClient, generatedNamespace.GetName(), csvAsub, subscriptionStateAtLatestChecker()) - require.NoError(GinkgoT(), err) - By(`Ensure csvNewA is not installed`) - _, err = crClient.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Get(context.Background(), csvNewA.Name, metav1.GetOptions{}) - require.Error(GinkgoT(), err) - By(`Ensure csvA still exists`) - _, err = fetchCSV(crClient, generatedNamespace.GetName(), csvA.Name, csvSucceededChecker) - require.NoError(GinkgoT(), err) - - By(`Update packagemanifest again`) - manifests = []registry.PackageManifest{ - { - PackageName: packageName1, - Channels: []registry.PackageChannel{ - {Name: stableChannel, CurrentCSVName: csvNewA.GetName()}, - }, - DefaultChannelName: stableChannel, - }, - { - PackageName: packageName2, - Channels: []registry.PackageChannel{ - {Name: stableChannel, CurrentCSVName: csvNewB.GetName()}, - }, - DefaultChannelName: stableChannel, - }, - } - updateInternalCatalog(GinkgoT(), kubeClient, crClient, catalogSourceName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{crd, crd2}, []operatorsv1alpha1.ClusterServiceVersion{csvA, csvB, csvNewA, csvNewB}, manifests) - - _, err = fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanDifferentChecker(subscription.Status.InstallPlanRef.Name)) - require.NoError(GinkgoT(), err) - By(`Ensure csvNewA is installed`) - _, err = fetchCSV(crClient, generatedNamespace.GetName(), csvNewA.Name, csvSucceededChecker) - require.NoError(GinkgoT(), err) - By(`Ensure csvNewB is installed`) - _, err = fetchCSV(crClient, generatedNamespace.GetName(), csvNewB.Name, csvSucceededChecker) - require.NoError(GinkgoT(), err) - }) - - When("A subscription is created for an operator that requires an API that is not available", func() { - var ( - c operatorclient.ClientInterface - crc versioned.Interface - teardown func() - cleanup func() - packages []registry.PackageManifest - crd = newCRD(genName("foo-")) - csvA operatorsv1alpha1.ClusterServiceVersion - csvB operatorsv1alpha1.ClusterServiceVersion - subName = genName("test-subscription-") - catSrcName = genName("test-catalog-") - ) - - BeforeEach(func() { - c = newKubeClient() - crc = newCRClient() - - packages = []registry.PackageManifest{ - { - PackageName: "test-package", - Channels: []registry.PackageChannel{ - {Name: "alpha", CurrentCSVName: "csvA"}, - }, - DefaultChannelName: "alpha", - }, - } - csvA = newCSV("csvA", generatedNamespace.GetName(), "", semver.MustParse("1.0.0"), nil, []apiextensionsv1.CustomResourceDefinition{crd}, nil) - - _, teardown = createInternalCatalogSource(c, ctx.Ctx().OperatorClient(), catSrcName, generatedNamespace.GetName(), packages, nil, []operatorsv1alpha1.ClusterServiceVersion{csvA}) - - By(`Ensure that the catalog source is resolved before we create a subscription.`) - _, err := fetchCatalogSourceOnStatus(crc, catSrcName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) - require.NoError(GinkgoT(), err) - - cleanup = createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subName, catSrcName, "test-package", "alpha", "", operatorsv1alpha1.ApprovalAutomatic) - }) - - AfterEach(func() { - cleanup() - teardown() - }) - - It("the subscription has a condition in it's status that indicates the resolution error", func() { - Eventually(func() (corev1.ConditionStatus, error) { - sub, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), subName, metav1.GetOptions{}) - if err != nil { - return corev1.ConditionUnknown, err - } - return sub.Status.GetCondition(operatorsv1alpha1.SubscriptionResolutionFailed).Status, nil - }).Should(Equal(corev1.ConditionTrue)) - }) - - When("the required API is made available", func() { - - BeforeEach(func() { - newPkg := registry.PackageManifest{ - PackageName: "another-package", - Channels: []registry.PackageChannel{ - {Name: "alpha", CurrentCSVName: "csvB"}, - }, - DefaultChannelName: "alpha", - } - packages = append(packages, newPkg) - - csvB = newCSV("csvB", generatedNamespace.GetName(), "", semver.MustParse("1.0.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) - - updateInternalCatalog(GinkgoT(), c, crc, catSrcName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{csvA, csvB}, packages) - }) - - It("the ResolutionFailed condition previously set in its status that indicated the resolution error is cleared off", func() { - Eventually(func() (corev1.ConditionStatus, error) { - sub, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), subName, metav1.GetOptions{}) - if err != nil { - return corev1.ConditionFalse, err - } - return sub.Status.GetCondition(operatorsv1alpha1.SubscriptionResolutionFailed).Status, nil - }).Should(Equal(corev1.ConditionUnknown)) - }) - }) - }) - - When("an unannotated ClusterServiceVersion exists with an associated Subscription", func() { - var ( - teardown func() - ) - - BeforeEach(func() { - teardown = func() {} - - packages := []registry.PackageManifest{ - { - PackageName: "package", - Channels: []registry.PackageChannel{ - {Name: "channel-x", CurrentCSVName: "csv-x"}, - {Name: "channel-y", CurrentCSVName: "csv-y"}, - }, - DefaultChannelName: "channel-x", - }, - } - - x := newCSV("csv-x", generatedNamespace.GetName(), "", semver.MustParse("1.0.0"), nil, nil, nil) - y := newCSV("csv-y", generatedNamespace.GetName(), "", semver.MustParse("1.0.0"), nil, nil, nil) - - _, teardown = createInternalCatalogSource(ctx.Ctx().KubeClient(), ctx.Ctx().OperatorClient(), "test-catalog", generatedNamespace.GetName(), packages, nil, []operatorsv1alpha1.ClusterServiceVersion{x, y}) - - createSubscriptionForCatalog(ctx.Ctx().OperatorClient(), generatedNamespace.GetName(), "test-subscription-x", "test-catalog", "package", "channel-x", "", operatorsv1alpha1.ApprovalAutomatic) - - Eventually(func() error { - var unannotated operatorsv1alpha1.ClusterServiceVersion - if err := ctx.Ctx().Client().Get(context.Background(), client.ObjectKey{Namespace: generatedNamespace.GetName(), Name: "csv-x"}, &unannotated); err != nil { - return err - } - if _, ok := unannotated.Annotations["operatorframework.io/properties"]; !ok { - return nil - } - delete(unannotated.Annotations, "operatorframework.io/properties") - return ctx.Ctx().Client().Update(context.Background(), &unannotated) - }).Should(Succeed()) - }) - - AfterEach(func() { - teardown() - }) - - It("uses inferred properties to prevent a duplicate installation from the same package ", func() { - createSubscriptionForCatalog(ctx.Ctx().OperatorClient(), generatedNamespace.GetName(), "test-subscription-y", "test-catalog", "package", "channel-y", "", operatorsv1alpha1.ApprovalAutomatic) - - Consistently(func() error { - var no operatorsv1alpha1.ClusterServiceVersion - return ctx.Ctx().Client().Get(context.Background(), client.ObjectKey{Namespace: generatedNamespace.GetName(), Name: "csv-y"}, &no) - }).ShouldNot(Succeed()) - }) - }) - - When("there exists a Subscription to an operator having dependency candidates in both default and nondefault channels", func() { - var ( - teardown func() - ) - - BeforeEach(func() { - teardown = func() {} - - packages := []registry.PackageManifest{ - { - PackageName: "dependency", - Channels: []registry.PackageChannel{ - {Name: "default", CurrentCSVName: "csv-dependency"}, - {Name: "nondefault", CurrentCSVName: "csv-dependency"}, - }, - DefaultChannelName: "default", - }, - { - PackageName: "root", - Channels: []registry.PackageChannel{ - {Name: "unimportant", CurrentCSVName: "csv-root"}, - }, - DefaultChannelName: "unimportant", - }, - } - - crds := []apiextensionsv1.CustomResourceDefinition{newCRD(genName("crd-"))} - csvs := []operatorsv1alpha1.ClusterServiceVersion{ - newCSV("csv-dependency", generatedNamespace.GetName(), "", semver.MustParse("1.0.0"), crds, nil, nil), - newCSV("csv-root", generatedNamespace.GetName(), "", semver.MustParse("1.0.0"), nil, crds, nil), - } - - _, teardown = createInternalCatalogSource(ctx.Ctx().KubeClient(), ctx.Ctx().OperatorClient(), "test-catalog", generatedNamespace.GetName(), packages, crds, csvs) - - createSubscriptionForCatalog(ctx.Ctx().OperatorClient(), generatedNamespace.GetName(), "test-subscription", "test-catalog", "root", "unimportant", "", operatorsv1alpha1.ApprovalAutomatic) - }) - - AfterEach(func() { - teardown() - }) - - It("should create a Subscription using the candidate's default channel", func() { - Eventually(func() ([]operatorsv1alpha1.Subscription, error) { - var list operatorsv1alpha1.SubscriptionList - if err := ctx.Ctx().Client().List(context.Background(), &list); err != nil { - return nil, err - } - return list.Items, nil - }).Should(ContainElement(WithTransform( - func(in operatorsv1alpha1.Subscription) operatorsv1alpha1.SubscriptionSpec { - return operatorsv1alpha1.SubscriptionSpec{ - CatalogSource: in.Spec.CatalogSource, - CatalogSourceNamespace: in.Spec.CatalogSourceNamespace, - Package: in.Spec.Package, - Channel: in.Spec.Channel, - } - }, - Equal(operatorsv1alpha1.SubscriptionSpec{ - CatalogSource: "test-catalog", - CatalogSourceNamespace: generatedNamespace.GetName(), - Package: "dependency", - Channel: "default", - }), - ))) - }) - }) - - It("unpacks bundle image", func() { - catsrc := &operatorsv1alpha1.CatalogSource{ - ObjectMeta: metav1.ObjectMeta{ - Name: genName("kiali-"), - Namespace: generatedNamespace.GetName(), - Labels: map[string]string{"olm.catalogSource": "kaili-catalog"}, - }, - Spec: operatorsv1alpha1.CatalogSourceSpec{ - Image: "quay.io/operator-framework/ci-index:latest", - SourceType: operatorsv1alpha1.SourceTypeGrpc, - GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ - SecurityContextConfig: operatorsv1alpha1.Restricted, - }, - }, - } - catsrc, err := crc.OperatorsV1alpha1().CatalogSources(catsrc.GetNamespace()).Create(context.Background(), catsrc, metav1.CreateOptions{}) - require.NoError(GinkgoT(), err) - defer func() { - Eventually(func() error { - return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), catsrc)) - }).Should(Succeed()) - }() - - By("waiting for the CatalogSource to be ready") - catsrc, err = fetchCatalogSourceOnStatus(crc, catsrc.GetName(), catsrc.GetNamespace(), catalogSourceRegistryPodSynced()) - require.NoError(GinkgoT(), err) - - By("generating a Subscription") - subName := genName("kiali-") - cleanUpSubscriptionFn := createSubscriptionForCatalog(crc, catsrc.GetNamespace(), subName, catsrc.GetName(), "kiali", stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) - defer cleanUpSubscriptionFn() - - By("waiting for the InstallPlan to get created for the subscription") - sub, err := fetchSubscription(crc, catsrc.GetNamespace(), subName, subscriptionHasInstallPlanChecker()) - require.NoError(GinkgoT(), err) - - By("waiting for the expected InstallPlan's execution to either fail or succeed") - ipName := sub.Status.InstallPlanRef.Name - ip, err := waitForInstallPlan(crc, ipName, sub.GetNamespace(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseFailed, operatorsv1alpha1.InstallPlanPhaseComplete)) - require.NoError(GinkgoT(), err) - require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, ip.Status.Phase, "InstallPlan not complete") - - By("ensuring the InstallPlan contains the steps resolved from the bundle image") - operatorName := "kiali-operator" - expectedSteps := map[registry.ResourceKey]struct{}{ - {Name: operatorName, Kind: "ClusterServiceVersion"}: {}, - {Name: "kialis.kiali.io", Kind: "CustomResourceDefinition"}: {}, - {Name: "monitoringdashboards.monitoring.kiali.io", Kind: "CustomResourceDefinition"}: {}, - {Name: operatorName, Kind: "ServiceAccount"}: {}, - {Name: operatorName, Kind: "ClusterRole"}: {}, - {Name: operatorName, Kind: "ClusterRoleBinding"}: {}, - } - require.Lenf(GinkgoT(), ip.Status.Plan, len(expectedSteps), "number of expected steps does not match installed: %v", ip.Status.Plan) - - for _, step := range ip.Status.Plan { - key := registry.ResourceKey{ - Name: step.Resource.Name, - Kind: step.Resource.Kind, - } - for expected := range expectedSteps { - if strings.HasPrefix(key.Name, expected.Name) && key.Kind == expected.Kind { - delete(expectedSteps, expected) - } - } - } - require.Lenf(GinkgoT(), expectedSteps, 0, "Actual resource steps do not match expected: %#v", expectedSteps) - }) - - When("unpacking bundle", func() { - var ( - magicCatalog *MagicCatalog - catalogSourceName string - subName string - ) - - BeforeEach(func() { - By("deploying the testing catalog") - provider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.1.0.yaml")) - Expect(err).To(BeNil()) - catalogSourceName = fmt.Sprintf("%s-catsrc", generatedNamespace.GetName()) - magicCatalog = NewMagicCatalog(ctx.Ctx().Client(), generatedNamespace.GetName(), catalogSourceName, provider) - Expect(magicCatalog.DeployCatalog(context.Background())).To(BeNil()) - - By("creating the testing subscription") - subName = fmt.Sprintf("%s-test-package-sub", generatedNamespace.GetName()) - createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subName, catalogSourceName, "test-package", stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) - - By("waiting until the subscription has an IP reference") - subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionHasInstallPlanChecker()) - Expect(err).Should(BeNil()) - - By("waiting for the v0.1.0 CSV to report a succeeded phase") - _, err = fetchCSV(crc, generatedNamespace.GetName(), subscription.Status.CurrentCSV, buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded)) - Expect(err).ShouldNot(HaveOccurred()) - }) - - It("should not report unpacking progress or errors after successfull unpacking", func() { - By("verifying that the subscription is not reporting unpacking progress") - Eventually( - func() (corev1.ConditionStatus, error) { - fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), subName, metav1.GetOptions{}) - if err != nil { - return "", err - } - cond := fetched.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleUnpacking) - return cond.Status, nil - }, - 5*time.Minute, - interval, - ).Should(Equal(corev1.ConditionUnknown)) - - By("verifying that the subscription is not reporting unpacking errors") - Eventually( - func() (corev1.ConditionStatus, error) { - fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), subName, metav1.GetOptions{}) - if err != nil { - return "", err - } - cond := fetched.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleUnpackFailed) - return cond.Status, nil - }, - 5*time.Minute, - interval, - ).Should(Equal(corev1.ConditionUnknown)) - }) - - Context("with bundle which OLM will fail to unpack", func() { - BeforeEach(func() { - By("patching the OperatorGroup to reduce the bundle unpacking timeout") - ogNN := types.NamespacedName{Name: operatorGroup.GetName(), Namespace: generatedNamespace.GetName()} - addBundleUnpackTimeoutOGAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "1s") - - By("updating the catalog with a broken v0.2.0 bundle image") - brokenProvider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.2.0-non-existent-tag.yaml")) - Expect(err).To(BeNil()) - err = magicCatalog.UpdateCatalog(context.Background(), brokenProvider) - Expect(err).To(BeNil()) - }) - - It("should expose a condition indicating failure to unpack", func() { - By("verifying that the subscription is reporting bundle unpack failure condition") - Eventually( - func() (string, error) { - fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), subName, metav1.GetOptions{}) - if err != nil { - return "", err - } - cond := fetched.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleUnpackFailed) - if cond.Status != corev1.ConditionTrue || cond.Reason != "BundleUnpackFailed" { - return "", fmt.Errorf("%s condition not found", operatorsv1alpha1.SubscriptionBundleUnpackFailed) - } - - return cond.Message, nil - }, - 5*time.Minute, - interval, - ).Should(ContainSubstring("bundle unpacking failed. Reason: DeadlineExceeded")) - - By("waiting for the subscription to maintain the example-operator.v0.1.0 status.currentCSV") - Consistently(subscriptionCurrentCSVGetter(crc, generatedNamespace.GetName(), subName)).Should(Equal("example-operator.v0.1.0")) - }) - - It("should be able to recover when catalog gets updated with a fixed version", func() { - By("patching the OperatorGroup to reduce the bundle unpacking timeout") - ogNN := types.NamespacedName{Name: operatorGroup.GetName(), Namespace: generatedNamespace.GetName()} - addBundleUnpackTimeoutOGAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "5m") - - By("updating the catalog with a fixed v0.2.0 bundle image") - brokenProvider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.2.0.yaml")) - Expect(err).To(BeNil()) - err = magicCatalog.UpdateCatalog(context.Background(), brokenProvider) - Expect(err).To(BeNil()) - - By("waiting for the subscription to have v0.2.0 installed") - _, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionHasCurrentCSV("example-operator.v0.2.0")) - Expect(err).Should(BeNil()) - }) - - It("should report deprecation conditions when package, channel, and bundle are referenced in an olm.deprecations object", func() { - By("patching the OperatorGroup to reduce the bundle unpacking timeout") - ogNN := types.NamespacedName{Name: operatorGroup.GetName(), Namespace: generatedNamespace.GetName()} - addBundleUnpackTimeoutOGAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "5m") - - By("updating the catalog with a fixed v0.2.0 bundle image marked deprecated") - provider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.2.0-deprecations.yaml")) - Expect(err).To(BeNil()) - err = magicCatalog.UpdateCatalog(context.Background(), provider) - Expect(err).To(BeNil()) - - By("waiting for the subscription to have v0.2.0 installed with a Bundle Deprecated condition") - sub, err := fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionHasCondition( - operatorsv1alpha1.SubscriptionBundleDeprecated, - corev1.ConditionTrue, - "", - "olm.bundle/example-operator.v0.2.0: bundle \"example-operator.v0.2.0\" has been deprecated. Please switch to a different one.")) - Expect(err).Should(BeNil()) - - By("checking for the deprecated conditions") - By(`Operator is deprecated at all three levels in the catalog`) - packageCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionPackageDeprecated) - Expect(packageCondition.Status).To(Equal(corev1.ConditionTrue)) - channelCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionChannelDeprecated) - Expect(channelCondition.Status).To(Equal(corev1.ConditionTrue)) - bundleCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleDeprecated) - Expect(bundleCondition.Status).To(Equal(corev1.ConditionTrue)) - - By("verifying that a roll-up condition is present containing all deprecation conditions") - By(`Roll-up condition should be present and contain deprecation messages from all three levels`) - rollUpCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionDeprecated) - Expect(rollUpCondition.Status).To(Equal(corev1.ConditionTrue)) - Expect(rollUpCondition.Message).To(ContainSubstring(packageCondition.Message)) - Expect(rollUpCondition.Message).To(ContainSubstring(channelCondition.Message)) - Expect(rollUpCondition.Message).To(ContainSubstring(bundleCondition.Message)) - - By("updating the catalog with a fixed v0.3.0 bundle image no longer marked deprecated") - provider, err = NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.3.0.yaml")) - Expect(err).To(BeNil()) - err = magicCatalog.UpdateCatalog(context.Background(), provider) - Expect(err).To(BeNil()) - - By("waiting for the subscription to have v0.3.0 installed with no deprecation message present") - _, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionDoesNotHaveCondition(operatorsv1alpha1.SubscriptionDeprecated)) - Expect(err).Should(BeNil()) - }) - - It("[FLAKE] should report only package and channel deprecation conditions when bundle is no longer deprecated", func() { - By("patching the OperatorGroup to reduce the bundle unpacking timeout") - ogNN := types.NamespacedName{Name: operatorGroup.GetName(), Namespace: generatedNamespace.GetName()} - addBundleUnpackTimeoutOGAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "5m") - - By("updating the catalog with a fixed v0.2.0 bundle image marked deprecated at all levels") - provider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.2.0-deprecations.yaml")) - Expect(err).To(BeNil()) - err = magicCatalog.UpdateCatalog(context.Background(), provider) - Expect(err).To(BeNil()) - - By("waiting for the subscription to have v0.2.0 installed with a Bundle Deprecated condition") - sub, err := fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionHasCondition( - operatorsv1alpha1.SubscriptionBundleDeprecated, - corev1.ConditionTrue, - "", - "olm.bundle/example-operator.v0.2.0: bundle \"example-operator.v0.2.0\" has been deprecated. Please switch to a different one.")) - Expect(err).Should(BeNil()) - - By("checking for the bundle deprecated condition") - bundleCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleDeprecated) - Expect(bundleCondition.Status).To(Equal(corev1.ConditionTrue)) - bundleDeprecatedMessage := bundleCondition.Message - - By("updating the catalog with a fixed v0.3.0 bundle marked partially deprecated") - provider, err = NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.3.0-deprecations.yaml")) - Expect(err).To(BeNil()) - err = magicCatalog.UpdateCatalog(context.Background(), provider) - Expect(err).To(BeNil()) - - By("waiting for the subscription to switch to v0.3.0") - sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionHasCurrentCSV("example-operator.v0.3.0")) - Expect(err).Should(BeNil()) - - By("waiting for the subscription to have be at latest known") - sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionStateAtLatestChecker()) - Expect(err).Should(BeNil()) - - By("waiting for the subscription to have v0.3.0 installed without a bundle deprecated condition") - sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, - subscriptionHasCondition( - operatorsv1alpha1.SubscriptionInstallPlanPending, - corev1.ConditionUnknown, - "", - "", - ), - ) - Expect(err).Should(BeNil()) - - By("waiting for the subscription to have v0.3.0 installed without a bundle deprecated condition") - sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, - subscriptionHasCondition( - operatorsv1alpha1.SubscriptionBundleDeprecated, - corev1.ConditionUnknown, - "", - "", - ), - ) - Expect(err).Should(BeNil()) - - By("checking for the deprecated conditions") - By(`Operator is deprecated at only Package and Channel levels`) - packageCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionPackageDeprecated) - Expect(packageCondition.Status).To(Equal(corev1.ConditionTrue)) - channelCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionChannelDeprecated) - Expect(channelCondition.Status).To(Equal(corev1.ConditionTrue)) - - By("verifying that a roll-up condition is present not containing bundle deprecation condition") - By(`Roll-up condition should be present and contain deprecation messages from Package and Channel levels`) - rollUpCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionDeprecated) - Expect(rollUpCondition.Status).To(Equal(corev1.ConditionTrue)) - Expect(rollUpCondition.Message).To(ContainSubstring(packageCondition.Message)) - Expect(rollUpCondition.Message).To(ContainSubstring(channelCondition.Message)) - Expect(rollUpCondition.Message).ToNot(ContainSubstring(bundleDeprecatedMessage)) - }) - - It("should report deprecated status when catalog is updated to deprecate an installed bundle", func() { - By("patching the OperatorGroup to reduce the bundle unpacking timeout") - ogNN := types.NamespacedName{Name: operatorGroup.GetName(), Namespace: generatedNamespace.GetName()} - addBundleUnpackTimeoutOGAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "5m") - - By("updating the catalog with a fixed v0.2.0 bundle not marked deprecated") - provider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.2.0.yaml")) - Expect(err).To(BeNil()) - err = magicCatalog.UpdateCatalog(context.Background(), provider) - Expect(err).To(BeNil()) - - By("waiting for the subscription to have v0.2.0 installed") - sub, err := fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionHasCurrentCSV("example-operator.v0.2.0")) - Expect(err).Should(BeNil()) - - By("the subscription should not be marked deprecated") - rollupCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionDeprecated) - Expect(rollupCondition.Status).To(Equal(corev1.ConditionUnknown)) - - By("updating the catalog with a fixed v0.2.0 bundle image marked deprecated at all levels") - provider, err = NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.2.0-deprecations.yaml")) - Expect(err).To(BeNil()) - err = magicCatalog.UpdateCatalog(context.Background(), provider) - Expect(err).To(BeNil()) - - By("checking for the bundle deprecated condition") - Eventually(func() (bool, error) { - sub, err := fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionHasCurrentCSV("example-operator.v0.2.0")) - if err != nil { - return false, err - } - cond := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionDeprecated) - if cond.Status != corev1.ConditionTrue { - return false, fmt.Errorf("%s condition not found", operatorsv1alpha1.SubscriptionDeprecated) - } - - return true, nil - }, 2*time.Minute, time.Second*10).Should(BeTrue()) - }) - }) - }) - When("bundle unpack retries are enabled", func() { - It("should retry failing unpack jobs", func() { - if ok, err := inKind(c); ok && err == nil { - Skip("This spec fails when run using KIND cluster. See https://github.com/operator-framework/operator-lifecycle-manager/issues/2420 for more details") - } else if err != nil { - Skip("Could not determine whether running in a kind cluster. Skipping.") - } - By("Ensuring a registry to host bundle images") - local, err := Local(c) - Expect(err).NotTo(HaveOccurred(), "cannot determine if test running locally or on CI: %s", err) - - var registryURL string - var copyImage func(dst, dstTag, src, srcTag string) error - if local { - registryURL, err = createDockerRegistry(c, generatedNamespace.GetName()) - Expect(err).NotTo(HaveOccurred(), "error creating container registry: %s", err) - defer deleteDockerRegistry(c, generatedNamespace.GetName()) - - By(`ensure registry pod is ready before attempting port-forwarding`) - _ = awaitPod(GinkgoT(), c, generatedNamespace.GetName(), registryName, podReady) - - err = registryPortForward(generatedNamespace.GetName()) - Expect(err).NotTo(HaveOccurred(), "port-forwarding local registry: %s", err) - copyImage = func(dst, dstTag, src, srcTag string) error { - if !strings.HasPrefix(src, "docker://") { - src = fmt.Sprintf("docker://%s", src) - } - if !strings.HasPrefix(dst, "docker://") { - dst = fmt.Sprintf("docker://%s", dst) - } - _, err := skopeoLocalCopy(dst, dstTag, src, srcTag) - return err - } - } else { - registryURL = fmt.Sprintf("%s/%s", openshiftregistryFQDN, generatedNamespace.GetName()) - registryAuthSecretName, err := getRegistryAuthSecretName(c, generatedNamespace.GetName()) - Expect(err).NotTo(HaveOccurred(), "error getting openshift registry authentication: %s", err) - copyImage = func(dst, dstTag, src, srcTag string) error { - if !strings.HasPrefix(src, "docker://") { - src = fmt.Sprintf("docker://%s", src) - } - if !strings.HasPrefix(dst, "docker://") { - dst = fmt.Sprintf("docker://%s", dst) - } - skopeoArgs := skopeoCopyCmd(dst, dstTag, src, srcTag, registryAuthSecretName) - err = createSkopeoPod(c, skopeoArgs, generatedNamespace.GetName(), registryAuthSecretName) - if err != nil { - return fmt.Errorf("error creating skopeo pod: %v", err) - } - - By(`wait for skopeo pod to exit successfully`) - awaitPod(GinkgoT(), c, generatedNamespace.GetName(), skopeo, func(pod *corev1.Pod) bool { - ctx.Ctx().Logf("skopeo pod status: %s (waiting for: %s)", pod.Status.Phase, corev1.PodSucceeded) - return pod.Status.Phase == corev1.PodSucceeded - }) - - if err := deleteSkopeoPod(c, generatedNamespace.GetName()); err != nil { - return fmt.Errorf("error deleting skopeo pod: %s", err) - } - return nil - } - } - - By(`The remote image to be copied onto the local registry`) - srcImage := "quay.io/olmtest/example-operator-bundle:" - srcTag := "0.1.0" - - By(`on-cluster image ref`) - bundleImage := registryURL + "/unpack-retry-bundle:" - bundleTag := genName("x") - - unpackRetryCatalog := fmt.Sprintf(` + var ( + generatedNamespace corev1.Namespace + operatorGroup operatorsv1.OperatorGroup + c operatorclient.ClientInterface + crc versioned.Interface + ) + + BeforeEach(func() { + c = ctx.Ctx().KubeClient() + crc = ctx.Ctx().OperatorClient() + + nsName := genName("subscription-e2e-") + operatorGroup = operatorsv1.OperatorGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-operatorgroup", nsName), + Namespace: nsName, + }, + } + generatedNamespace = SetupGeneratedTestNamespaceWithOperatorGroup(nsName, operatorGroup) + }) + + AfterEach(func() { + TeardownNamespace(generatedNamespace.GetName()) + }) + + When("an entry in the middle of a channel does not provide a required GVK", func() { + var ( + teardown func() + ) + + BeforeEach(func() { + teardown = func() {} + packages := []registry.PackageManifest{ + { + PackageName: "dependency", + Channels: []registry.PackageChannel{ + {Name: "channel-dependency", CurrentCSVName: "csv-dependency-3"}, + }, + DefaultChannelName: "channel-dependency", + }, + { + PackageName: "root", + Channels: []registry.PackageChannel{ + {Name: "channel-root", CurrentCSVName: "csv-root"}, + }, + DefaultChannelName: "channel-root", + }, + } + + crds := []apiextensionsv1.CustomResourceDefinition{newCRD(genName("crd-"))} + csvs := []operatorsv1alpha1.ClusterServiceVersion{ + newCSV("csv-dependency-1", generatedNamespace.GetName(), "", semver.MustParse("1.0.0"), crds, nil, nil), + newCSV("csv-dependency-2", generatedNamespace.GetName(), "csv-dependency-1", semver.MustParse("2.0.0"), nil, nil, nil), + newCSV("csv-dependency-3", generatedNamespace.GetName(), "csv-dependency-2", semver.MustParse("3.0.0"), crds, nil, nil), + newCSV("csv-root", generatedNamespace.GetName(), "", semver.MustParse("1.0.0"), nil, crds, nil), + } + + _, teardown = createInternalCatalogSource(ctx.Ctx().KubeClient(), ctx.Ctx().OperatorClient(), "test-catalog", generatedNamespace.GetName(), packages, crds, csvs) + _, err := fetchCatalogSourceOnStatus(ctx.Ctx().OperatorClient(), "test-catalog", generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) + Expect(err).NotTo(HaveOccurred()) + + createSubscriptionForCatalog(ctx.Ctx().OperatorClient(), generatedNamespace.GetName(), "test-subscription", "test-catalog", "root", "channel-root", "", operatorsv1alpha1.ApprovalAutomatic) + }) + + AfterEach(func() { + teardown() + }) + + It("should create a Subscription for the latest entry providing the required GVK", func() { + Eventually(func() ([]operatorsv1alpha1.Subscription, error) { + var list operatorsv1alpha1.SubscriptionList + if err := ctx.Ctx().Client().List(context.Background(), &list); err != nil { + return nil, err + } + return list.Items, nil + }).Should(ContainElement(WithTransform( + func(in operatorsv1alpha1.Subscription) operatorsv1alpha1.SubscriptionSpec { + return operatorsv1alpha1.SubscriptionSpec{ + CatalogSource: in.Spec.CatalogSource, + CatalogSourceNamespace: in.Spec.CatalogSourceNamespace, + Package: in.Spec.Package, + Channel: in.Spec.Channel, + StartingCSV: in.Spec.StartingCSV, + } + }, + Equal(operatorsv1alpha1.SubscriptionSpec{ + CatalogSource: "test-catalog", + CatalogSourceNamespace: generatedNamespace.GetName(), + Package: "dependency", + Channel: "channel-dependency", + StartingCSV: "csv-dependency-3", + }), + ))) + }) + }) + + It("creation if not installed", func() { + By(` I. Creating a new subscription`) + By(` A. If package is not installed, creating a subscription should install latest version`) + + defer func() { + if env := os.Getenv("SKIP_CLEANUP"); env != "" { + fmt.Printf("Skipping cleanup of subscriptions in namespace %s\n", generatedNamespace.GetName()) + return + } + require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{})) + }() + + By("creating a catalog") + require.NoError(GinkgoT(), initCatalog(GinkgoT(), generatedNamespace.GetName(), c, crc)) + + By(fmt.Sprintf("creating a subscription: %s/%s", generatedNamespace.GetName(), testSubscriptionName)) + cleanup, _ := createSubscription(GinkgoT(), crc, generatedNamespace.GetName(), testSubscriptionName, testPackageName, betaChannel, operatorsv1alpha1.ApprovalAutomatic) + + defer cleanup() + + By("waiting for the subscription to have a current CSV and be at latest") + var currentCSV string + Eventually(func() bool { + fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), testSubscriptionName, metav1.GetOptions{}) + if err != nil { + return false + } + if fetched != nil { + currentCSV = fetched.Status.CurrentCSV + return fetched.Status.State == operatorsv1alpha1.SubscriptionStateAtLatest + } + return false + }, 5*time.Minute, 10*time.Second).Should(BeTrue()) + + csv, err := fetchCSV(crc, generatedNamespace.GetName(), currentCSV, buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded)) + require.NoError(GinkgoT(), err) + + By(`Check for the olm.package property as a proxy for`) + By(`verifying that the annotation value is reasonable.`) + Expect( + projection.PropertyListFromPropertiesAnnotation(csv.GetAnnotations()["operatorframework.io/properties"]), + ).To(ContainElement( + ®istryapi.Property{Type: "olm.package", Value: `{"packageName":"myapp","version":"0.1.1"}`}, + )) + }) + + It("creation using existing CSV", func() { + By(` I. Creating a new subscription`) + By(` B. If package is already installed, creating a subscription should upgrade it to the latest version`) + + defer func() { + require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{})) + }() + require.NoError(GinkgoT(), initCatalog(GinkgoT(), generatedNamespace.GetName(), c, crc)) + + By(`Will be cleaned up by the upgrade process`) + _, err := createCSV(c, crc, stableCSV, generatedNamespace.GetName(), false, false) + require.NoError(GinkgoT(), err) + + subscriptionCleanup, _ := createSubscription(GinkgoT(), crc, generatedNamespace.GetName(), testSubscriptionName, testPackageName, alphaChannel, operatorsv1alpha1.ApprovalAutomatic) + defer subscriptionCleanup() + + subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), testSubscriptionName, subscriptionStateAtLatestChecker()) + require.NoError(GinkgoT(), err) + require.NotNil(GinkgoT(), subscription) + _, err = fetchCSV(crc, generatedNamespace.GetName(), subscription.Status.CurrentCSV, buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded)) + require.NoError(GinkgoT(), err) + }) + It("skip range", func() { + + crdPlural := genName("ins") + crdName := crdPlural + ".cluster.com" + crd := apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: crdName, + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "cluster.com", + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ + { + Name: "v1alpha1", + Served: true, + Storage: true, + Schema: &apiextensionsv1.CustomResourceValidation{ + OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ + Type: "object", + Description: "my crd schema", + }, + }, + }, + }, + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Plural: crdPlural, + Singular: crdPlural, + Kind: crdPlural, + ListKind: "list" + crdPlural, + }, + Scope: apiextensionsv1.NamespaceScoped, + }, + } + + mainPackageName := genName("nginx-") + mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName) + updatedPackageStable := fmt.Sprintf("%s-updated", mainPackageName) + stableChannel := "stable" + mainCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0-1556661347"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) + updatedCSV := newCSV(updatedPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0-1556661832"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) + updatedCSV.SetAnnotations(map[string]string{"olm.skipRange": ">=0.1.0-1556661347 <0.1.0-1556661832"}) + + c := newKubeClient() + crc := newCRClient() + defer func() { + require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{})) + }() + + mainCatalogName := genName("mock-ocs-main-") + + By(`Create separate manifests for each CatalogSource`) + mainManifests := []registry.PackageManifest{ + { + PackageName: mainPackageName, + Channels: []registry.PackageChannel{ + {Name: stableChannel, CurrentCSVName: mainPackageStable}, + }, + DefaultChannelName: stableChannel, + }, + } + updatedManifests := []registry.PackageManifest{ + { + PackageName: mainPackageName, + Channels: []registry.PackageChannel{ + {Name: stableChannel, CurrentCSVName: updatedPackageStable}, + }, + DefaultChannelName: stableChannel, + }, + } + + By(`Create catalog source`) + _, cleanupMainCatalogSource := createInternalCatalogSource(c, crc, mainCatalogName, generatedNamespace.GetName(), mainManifests, []apiextensionsv1.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{mainCSV}) + defer cleanupMainCatalogSource() + By(`Attempt to get the catalog source before creating subscription`) + _, err := fetchCatalogSourceOnStatus(crc, mainCatalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) + require.NoError(GinkgoT(), err) + + By(`Create a subscription`) + subscriptionName := genName("sub-nginx-") + subscriptionCleanup := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) + defer subscriptionCleanup() + + By(`Wait for csv to install`) + firstCSV, err := fetchCSV(crc, generatedNamespace.GetName(), mainCSV.GetName(), csvSucceededChecker) + require.NoError(GinkgoT(), err) + + By(`Update catalog with a new csv in the channel with a skip range`) + updateInternalCatalog(GinkgoT(), c, crc, mainCatalogName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{updatedCSV}, updatedManifests) + + By(`Wait for csv to update`) + finalCSV, err := fetchCSV(crc, generatedNamespace.GetName(), updatedCSV.GetName(), csvSucceededChecker) + require.NoError(GinkgoT(), err) + + By(`Ensure we set the replacement field based on the registry data`) + require.Equal(GinkgoT(), firstCSV.GetName(), finalCSV.Spec.Replaces) + }) + + It("creation manual approval", func() { + By(`If installPlanApproval is set to manual, the installplans created should be created with approval: manual`) + + defer func() { + require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{})) + }() + require.NoError(GinkgoT(), initCatalog(GinkgoT(), generatedNamespace.GetName(), c, crc)) + + subscriptionCleanup, _ := createSubscription(GinkgoT(), crc, generatedNamespace.GetName(), "manual-subscription", testPackageName, stableChannel, operatorsv1alpha1.ApprovalManual) + defer subscriptionCleanup() + + subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), "manual-subscription", subscriptionHasCondition(operatorsv1alpha1.SubscriptionInstallPlanPending, corev1.ConditionTrue, string(operatorsv1alpha1.InstallPlanPhaseRequiresApproval), "")) + require.NoError(GinkgoT(), err) + require.NotNil(GinkgoT(), subscription) + + installPlan, err := fetchInstallPlanWithNamespace(GinkgoT(), crc, subscription.Status.Install.Name, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseRequiresApproval)) + require.NoError(GinkgoT(), err) + require.NotNil(GinkgoT(), installPlan) + + require.Equal(GinkgoT(), operatorsv1alpha1.ApprovalManual, installPlan.Spec.Approval) + require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseRequiresApproval, installPlan.Status.Phase) + + By(`Delete the current installplan`) + err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Delete(context.Background(), installPlan.Name, metav1.DeleteOptions{}) + require.NoError(GinkgoT(), err) + + var ipName string + Eventually(func() bool { + fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), "manual-subscription", metav1.GetOptions{}) + if err != nil { + return false + } + if fetched.Status.Install != nil { + ipName = fetched.Status.Install.Name + return fetched.Status.Install.Name != installPlan.Name + } + return false + }, 5*time.Minute, 10*time.Second).Should(BeTrue()) + + By(`Fetch new installplan`) + newInstallPlan, err := fetchInstallPlanWithNamespace(GinkgoT(), crc, ipName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseRequiresApproval)) + require.NoError(GinkgoT(), err) + require.NotNil(GinkgoT(), newInstallPlan) + + require.NotEqual(GinkgoT(), installPlan.Name, newInstallPlan.Name, "expected new installplan recreated") + require.Equal(GinkgoT(), operatorsv1alpha1.ApprovalManual, newInstallPlan.Spec.Approval) + require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseRequiresApproval, newInstallPlan.Status.Phase) + + By(`Set the InstallPlan's approved to True`) + Eventually(Apply(newInstallPlan, func(p *operatorsv1alpha1.InstallPlan) error { + + p.Spec.Approved = true + return nil + })).Should(Succeed()) + + subscription, err = fetchSubscription(crc, generatedNamespace.GetName(), "manual-subscription", subscriptionStateAtLatestChecker()) + require.NoError(GinkgoT(), err) + require.NotNil(GinkgoT(), subscription) + + _, err = fetchCSV(crc, generatedNamespace.GetName(), subscription.Status.CurrentCSV, buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded)) + require.NoError(GinkgoT(), err) + }) + + It("with starting CSV", func() { + + crdPlural := genName("ins") + crdName := crdPlural + ".cluster.com" + + crd := apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: crdName, + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "cluster.com", + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ + { + Name: "v1alpha1", + Served: true, + Storage: true, + Schema: &apiextensionsv1.CustomResourceValidation{ + OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ + Type: "object", + Description: "my crd schema", + }, + }, + }, + }, + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Plural: crdPlural, + Singular: crdPlural, + Kind: crdPlural, + ListKind: "list" + crdPlural, + }, + Scope: apiextensionsv1.NamespaceScoped, + }, + } + + By(`Create CSV`) + packageName := genName("nginx-") + stableChannel := "stable" + + csvA := newCSV("nginx-a", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) + csvB := newCSV("nginx-b", generatedNamespace.GetName(), "nginx-a", semver.MustParse("0.2.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) + + By(`Create PackageManifests`) + manifests := []registry.PackageManifest{ + { + PackageName: packageName, + Channels: []registry.PackageChannel{ + {Name: stableChannel, CurrentCSVName: csvB.GetName()}, + }, + DefaultChannelName: stableChannel, + }, + } + + By(`Create the CatalogSource`) + catalogSourceName := genName("mock-nginx-") + _, cleanupCatalogSource := createInternalCatalogSource(c, crc, catalogSourceName, generatedNamespace.GetName(), manifests, []apiextensionsv1.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{csvA, csvB}) + defer cleanupCatalogSource() + + By(`Attempt to get the catalog source before creating install plan`) + _, err := fetchCatalogSourceOnStatus(crc, catalogSourceName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) + require.NoError(GinkgoT(), err) + + subscriptionName := genName("sub-nginx-") + cleanupSubscription := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, catalogSourceName, packageName, stableChannel, csvA.GetName(), operatorsv1alpha1.ApprovalManual) + defer cleanupSubscription() + + subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) + require.NoError(GinkgoT(), err) + require.NotNil(GinkgoT(), subscription) + + installPlanName := subscription.Status.Install.Name + + By(`Wait for InstallPlan to be status: Complete before checking resource presence`) + requiresApprovalChecker := buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseRequiresApproval) + fetchedInstallPlan, err := fetchInstallPlanWithNamespace(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), requiresApprovalChecker) + require.NoError(GinkgoT(), err) + + By(`Ensure that only 1 installplan was created`) + ips, err := crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).List(context.Background(), metav1.ListOptions{}) + require.NoError(GinkgoT(), err) + require.Len(GinkgoT(), ips.Items, 1) + + By(`Ensure that csvA and its crd are found in the plan`) + csvFound := false + crdFound := false + for _, s := range fetchedInstallPlan.Status.Plan { + require.Equal(GinkgoT(), csvA.GetName(), s.Resolving, "unexpected resolution found") + require.Equal(GinkgoT(), operatorsv1alpha1.StepStatusUnknown, s.Status, "status should be unknown") + require.Equal(GinkgoT(), catalogSourceName, s.Resource.CatalogSource, "incorrect catalogsource on step resource") + switch kind := s.Resource.Kind; kind { + case operatorsv1alpha1.ClusterServiceVersionKind: + require.Equal(GinkgoT(), csvA.GetName(), s.Resource.Name, "unexpected csv found") + csvFound = true + case "CustomResourceDefinition": + require.Equal(GinkgoT(), crdName, s.Resource.Name, "unexpected crd found") + crdFound = true + default: + GinkgoT().Fatalf("unexpected resource kind found in installplan: %s", kind) + } + } + require.True(GinkgoT(), csvFound, "expected csv not found in installplan") + require.True(GinkgoT(), crdFound, "expected crd not found in installplan") + + By(`Ensure that csvB is not found in the plan`) + csvFound = false + for _, s := range fetchedInstallPlan.Status.Plan { + require.Equal(GinkgoT(), csvA.GetName(), s.Resolving, "unexpected resolution found") + require.Equal(GinkgoT(), operatorsv1alpha1.StepStatusUnknown, s.Status, "status should be unknown") + require.Equal(GinkgoT(), catalogSourceName, s.Resource.CatalogSource, "incorrect catalogsource on step resource") + switch kind := s.Resource.Kind; kind { + case operatorsv1alpha1.ClusterServiceVersionKind: + if s.Resource.Name == csvB.GetName() { + csvFound = true + } + } + } + require.False(GinkgoT(), csvFound, "expected csv not found in installplan") + + By(`Approve the installplan and wait for csvA to be installed`) + fetchedInstallPlan.Spec.Approved = true + _, err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Update(context.Background(), fetchedInstallPlan, metav1.UpdateOptions{}) + require.NoError(GinkgoT(), err) + + _, err = fetchCSV(crc, generatedNamespace.GetName(), csvA.GetName(), csvSucceededChecker) + require.NoError(GinkgoT(), err) + + By(`Wait for the subscription to begin upgrading to csvB`) + By(`The upgrade changes the installplanref on the subscription`) + Eventually(func() (bool, error) { + subscription, err = crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), subscriptionName, metav1.GetOptions{}) + return subscription != nil && subscription.Status.InstallPlanRef.Name != fetchedInstallPlan.GetName() && subscription.Status.State == operatorsv1alpha1.SubscriptionStateUpgradePending, err + }, 5*time.Minute, 1*time.Second).Should(BeTrue(), "expected new installplan for upgraded csv") + + upgradeInstallPlan, err := fetchInstallPlanWithNamespace(GinkgoT(), crc, subscription.Status.InstallPlanRef.Name, generatedNamespace.GetName(), requiresApprovalChecker) + require.NoError(GinkgoT(), err) + + By(`Approve the upgrade installplan and wait for`) + upgradeInstallPlan.Spec.Approved = true + _, err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Update(context.Background(), upgradeInstallPlan, metav1.UpdateOptions{}) + require.NoError(GinkgoT(), err) + + _, err = fetchCSV(crc, generatedNamespace.GetName(), csvB.GetName(), csvSucceededChecker) + require.NoError(GinkgoT(), err) + + By(`Ensure that 2 installplans were created`) + ips, err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).List(context.Background(), metav1.ListOptions{}) + require.NoError(GinkgoT(), err) + require.Len(GinkgoT(), ips.Items, 2) + }) + + It("[FLAKE] updates multiple intermediates", func() { + By(`issue: https://github.com/operator-framework/operator-lifecycle-manager/issues/2635`) + + crd := newCRD("ins") + + By(`Create CSV`) + packageName := genName("nginx-") + stableChannel := "stable" + + csvA := newCSV("nginx-a", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) + csvB := newCSV("nginx-b", generatedNamespace.GetName(), "nginx-a", semver.MustParse("0.2.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) + csvC := newCSV("nginx-c", generatedNamespace.GetName(), "nginx-b", semver.MustParse("0.3.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) + + By(`Create PackageManifests`) + manifests := []registry.PackageManifest{ + { + PackageName: packageName, + Channels: []registry.PackageChannel{ + {Name: stableChannel, CurrentCSVName: csvA.GetName()}, + }, + DefaultChannelName: stableChannel, + }, + } + + By(`Create the CatalogSource with just one version`) + catalogSourceName := genName("mock-nginx-") + _, cleanupCatalogSource := createInternalCatalogSource(c, crc, catalogSourceName, generatedNamespace.GetName(), manifests, []apiextensionsv1.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{csvA}) + defer cleanupCatalogSource() + + By(`Attempt to get the catalog source before creating install plan`) + _, err := fetchCatalogSourceOnStatus(crc, catalogSourceName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) + require.NoError(GinkgoT(), err) + + subscriptionName := genName("sub-nginx-") + cleanupSubscription := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, catalogSourceName, packageName, stableChannel, csvA.GetName(), operatorsv1alpha1.ApprovalAutomatic) + defer cleanupSubscription() + + subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) + require.NoError(GinkgoT(), err) + require.NotNil(GinkgoT(), subscription) + + By(`Wait for csvA to be installed`) + _, err = fetchCSV(crc, generatedNamespace.GetName(), csvA.GetName(), csvSucceededChecker) + require.NoError(GinkgoT(), err) + + By(`Set up async watches that will fail the test if csvB doesn't get created in between csvA and csvC`) + var wg sync.WaitGroup + go func(t GinkgoTInterface) { + defer GinkgoRecover() + wg.Add(1) + defer wg.Done() + _, err := fetchCSV(crc, generatedNamespace.GetName(), csvB.GetName(), csvReplacingChecker) + require.NoError(GinkgoT(), err) + }(GinkgoT()) + By(`Update the catalog to include multiple updates`) + packages := []registry.PackageManifest{ + { + PackageName: packageName, + Channels: []registry.PackageChannel{ + {Name: stableChannel, CurrentCSVName: csvC.GetName()}, + }, + DefaultChannelName: stableChannel, + }, + } + + updateInternalCatalog(GinkgoT(), c, crc, catalogSourceName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{csvA, csvB, csvC}, packages) + + By(`wait for checks on intermediate csvs to succeed`) + wg.Wait() + + By(`Wait for csvC to be installed`) + _, err = fetchCSV(crc, generatedNamespace.GetName(), csvC.GetName(), csvSucceededChecker) + require.NoError(GinkgoT(), err) + + By(`Should eventually GC the CSVs`) + err = waitForCsvToDelete(generatedNamespace.GetName(), csvA.Name, crc) + Expect(err).ShouldNot(HaveOccurred()) + + err = waitForCsvToDelete(generatedNamespace.GetName(), csvB.Name, crc) + Expect(err).ShouldNot(HaveOccurred()) + + By(`TODO: check installplans, subscription status, etc`) + }) + + It("updates existing install plan", func() { + By(`TestSubscriptionUpdatesExistingInstallPlan ensures that an existing InstallPlan has the appropriate approval requirement from Subscription.`) + + Skip("ToDo: This test was skipped before ginkgo conversion") + + By(`Create CSV`) + packageName := genName("nginx-") + stableChannel := "stable" + + csvA := newCSV("nginx-a", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), nil, nil, nil) + csvB := newCSV("nginx-b", generatedNamespace.GetName(), "nginx-a", semver.MustParse("0.2.0"), nil, nil, nil) + + By(`Create PackageManifests`) + manifests := []registry.PackageManifest{ + { + PackageName: packageName, + Channels: []registry.PackageChannel{ + {Name: stableChannel, CurrentCSVName: csvB.GetName()}, + }, + DefaultChannelName: stableChannel, + }, + } + + By(`Create the CatalogSource with just one version`) + catalogSourceName := genName("mock-nginx-") + _, cleanupCatalogSource := createInternalCatalogSource(c, crc, catalogSourceName, generatedNamespace.GetName(), manifests, nil, []operatorsv1alpha1.ClusterServiceVersion{csvA, csvB}) + defer cleanupCatalogSource() + + By(`Attempt to get the catalog source before creating install plan`) + _, err := fetchCatalogSourceOnStatus(crc, catalogSourceName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) + require.NoError(GinkgoT(), err) + + By(`Create a subscription to just get an InstallPlan for csvB`) + subscriptionName := genName("sub-nginx-") + createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, catalogSourceName, packageName, stableChannel, csvB.GetName(), operatorsv1alpha1.ApprovalAutomatic) + + By(`Wait for csvB to be installed`) + _, err = fetchCSV(crc, generatedNamespace.GetName(), csvB.GetName(), csvSucceededChecker) + require.NoError(GinkgoT(), err) + + subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) + fetchedInstallPlan, err := fetchInstallPlanWithNamespace(GinkgoT(), crc, subscription.Status.InstallPlanRef.Name, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete)) + + By(`Delete this subscription`) + err = crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), *metav1.NewDeleteOptions(0), metav1.ListOptions{}) + require.NoError(GinkgoT(), err) + By(`Delete orphaned csvB`) + require.NoError(GinkgoT(), crc.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Delete(context.Background(), csvB.GetName(), metav1.DeleteOptions{})) + + By(`Create an InstallPlan for csvB`) + ip := &operatorsv1alpha1.InstallPlan{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "install-", + Namespace: generatedNamespace.GetName(), + }, + Spec: operatorsv1alpha1.InstallPlanSpec{ + ClusterServiceVersionNames: []string{csvB.GetName()}, + Approval: operatorsv1alpha1.ApprovalAutomatic, + Approved: false, + }, + } + ip2, err := crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Create(context.Background(), ip, metav1.CreateOptions{}) + require.NoError(GinkgoT(), err) + + ip2.Status = operatorsv1alpha1.InstallPlanStatus{ + Plan: fetchedInstallPlan.Status.Plan, + CatalogSources: []string{catalogSourceName}, + } + + _, err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).UpdateStatus(context.Background(), ip2, metav1.UpdateOptions{}) + require.NoError(GinkgoT(), err) + + subscriptionName = genName("sub-nginx-") + cleanupSubscription := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, catalogSourceName, packageName, stableChannel, csvA.GetName(), operatorsv1alpha1.ApprovalManual) + defer cleanupSubscription() + + subscription, err = fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) + require.NoError(GinkgoT(), err) + require.NotNil(GinkgoT(), subscription) + + installPlanName := subscription.Status.Install.Name + + By(`Wait for InstallPlan to be status: Complete before checking resource presence`) + requiresApprovalChecker := buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseRequiresApproval) + fetchedInstallPlan, err = fetchInstallPlanWithNamespace(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), requiresApprovalChecker) + require.NoError(GinkgoT(), err) + + By(`Approve the installplan and wait for csvA to be installed`) + fetchedInstallPlan.Spec.Approved = true + _, err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Update(context.Background(), fetchedInstallPlan, metav1.UpdateOptions{}) + require.NoError(GinkgoT(), err) + + By(`Wait for csvA to be installed`) + _, err = fetchCSV(crc, generatedNamespace.GetName(), csvA.GetName(), csvSucceededChecker) + require.NoError(GinkgoT(), err) + + By(`Wait for the subscription to begin upgrading to csvB`) + subscription, err = fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionStateUpgradePendingChecker()) + require.NoError(GinkgoT(), err) + + By(`Fetch existing csvB installPlan`) + fetchedInstallPlan, err = fetchInstallPlanWithNamespace(GinkgoT(), crc, subscription.Status.InstallPlanRef.Name, generatedNamespace.GetName(), requiresApprovalChecker) + require.NoError(GinkgoT(), err) + require.Equal(GinkgoT(), ip2.GetName(), subscription.Status.InstallPlanRef.Name, "expected new installplan is the same with pre-exising one") + + By(`Approve the installplan and wait for csvB to be installed`) + fetchedInstallPlan.Spec.Approved = true + _, err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Update(context.Background(), fetchedInstallPlan, metav1.UpdateOptions{}) + require.NoError(GinkgoT(), err) + + By(`Wait for csvB to be installed`) + _, err = fetchCSV(crc, generatedNamespace.GetName(), csvB.GetName(), csvSucceededChecker) + require.NoError(GinkgoT(), err) + }) + + Describe("puppeting CatalogSource health status", func() { + var ( + getOpts metav1.GetOptions + deleteOpts *metav1.DeleteOptions + ) + + BeforeEach(func() { + getOpts = metav1.GetOptions{} + deleteOpts = &metav1.DeleteOptions{} + }) + + AfterEach(func() { + err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{}) + Expect(err).NotTo(HaveOccurred()) + }) + + When("missing target catalog", func() { + It("should surface the missing catalog", func() { + By(`TestSubscriptionStatusMissingTargetCatalogSource ensures that a Subscription has the appropriate status condition when`) + By(`its target catalog is missing.`) + By(` BySteps:`) + By(`1. Generate an initial CatalogSource in the target namespace`) + By(`2. Generate Subscription, "sub", targetting non-existent CatalogSource, "missing"`) + By(`3. Wait for sub status to show SubscriptionCatalogSourcesUnhealthy with status True, reason CatalogSourcesUpdated, and appropriate missing message`) + By(`4. Update sub's spec to target the "mysubscription"`) + By(`5. Wait for sub's status to show SubscriptionCatalogSourcesUnhealthy with status False, reason AllCatalogSourcesHealthy, and reason "all available catalogsources are healthy"`) + By(`6. Wait for sub to succeed`) + err := initCatalog(GinkgoT(), generatedNamespace.GetName(), c, crc) + Expect(err).NotTo(HaveOccurred()) + + missingName := "missing" + cleanup := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), testSubscriptionName, missingName, testPackageName, betaChannel, "", operatorsv1alpha1.ApprovalAutomatic) + defer cleanup() + + By("detecting its absence") + sub, err := fetchSubscription(crc, generatedNamespace.GetName(), testSubscriptionName, subscriptionHasCondition(operatorsv1alpha1.SubscriptionCatalogSourcesUnhealthy, corev1.ConditionTrue, operatorsv1alpha1.UnhealthyCatalogSourceFound, fmt.Sprintf("targeted catalogsource %s/%s missing", generatedNamespace.GetName(), missingName))) + Expect(err).NotTo(HaveOccurred()) + Expect(sub).ToNot(BeNil()) + + By("updating the subscription to target an existing catsrc") + Eventually(func() error { + sub, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), testSubscriptionName, metav1.GetOptions{}) + if err != nil { + return err + } + if sub == nil { + return fmt.Errorf("subscription is nil") + } + sub.Spec.CatalogSource = catalogSourceName + _, err = crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Update(context.Background(), sub, metav1.UpdateOptions{}) + return err + }).Should(Succeed()) + + By(`Wait for SubscriptionCatalogSourcesUnhealthy to be false`) + By("detecting a new existing target") + _, err = fetchSubscription(crc, generatedNamespace.GetName(), testSubscriptionName, subscriptionHasCondition(operatorsv1alpha1.SubscriptionCatalogSourcesUnhealthy, corev1.ConditionFalse, operatorsv1alpha1.AllCatalogSourcesHealthy, "all available catalogsources are healthy")) + Expect(err).NotTo(HaveOccurred()) + + By(`Wait for success`) + _, err = fetchSubscription(crc, generatedNamespace.GetName(), testSubscriptionName, subscriptionStateAtLatestChecker()) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + When("the target catalog's sourceType", func() { + Context("is unknown", func() { + It("should surface catalog health", func() { + cs := &operatorsv1alpha1.CatalogSource{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.CatalogSourceKind, + APIVersion: operatorsv1alpha1.CatalogSourceCRDAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "cs", + }, + Spec: operatorsv1alpha1.CatalogSourceSpec{ + SourceType: "goose", + GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ + SecurityContextConfig: operatorsv1alpha1.Restricted, + }, + }, + } + + var err error + cs, err = crc.OperatorsV1alpha1().CatalogSources(generatedNamespace.GetName()).Create(context.Background(), cs, metav1.CreateOptions{}) + defer func() { + err = crc.OperatorsV1alpha1().CatalogSources(cs.GetNamespace()).Delete(context.Background(), cs.GetName(), *deleteOpts) + Expect(err).ToNot(HaveOccurred()) + }() + + subName := genName("sub-") + cleanup := createSubscriptionForCatalog( + crc, + cs.GetNamespace(), + subName, + cs.GetName(), + testPackageName, + betaChannel, + "", + operatorsv1alpha1.ApprovalManual, + ) + defer cleanup() + + var sub *operatorsv1alpha1.Subscription + sub, err = fetchSubscription( + crc, + cs.GetNamespace(), + subName, + subscriptionHasCondition( + operatorsv1alpha1.SubscriptionCatalogSourcesUnhealthy, + corev1.ConditionTrue, + operatorsv1alpha1.UnhealthyCatalogSourceFound, + fmt.Sprintf("targeted catalogsource %s/%s unhealthy", cs.GetNamespace(), cs.GetName()), + ), + ) + Expect(err).NotTo(HaveOccurred()) + Expect(sub).ToNot(BeNil()) + + By(`Get the latest CatalogSource`) + cs, err = crc.OperatorsV1alpha1().CatalogSources(cs.GetNamespace()).Get(context.Background(), cs.GetName(), getOpts) + Expect(err).NotTo(HaveOccurred()) + Expect(cs).ToNot(BeNil()) + }) + }) + + Context("is grpc and its spec is missing the address and image fields", func() { + It("should surface catalog health", func() { + By(`Create a CatalogSource pointing to the grpc pod`) + cs := &operatorsv1alpha1.CatalogSource{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.CatalogSourceKind, + APIVersion: operatorsv1alpha1.CatalogSourceCRDAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: genName("cs-"), + Namespace: generatedNamespace.GetName(), + }, + Spec: operatorsv1alpha1.CatalogSourceSpec{ + SourceType: operatorsv1alpha1.SourceTypeGrpc, + GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ + SecurityContextConfig: operatorsv1alpha1.Restricted, + }, + }, + } + + var err error + cs, err = crc.OperatorsV1alpha1().CatalogSources(generatedNamespace.GetName()).Create(context.Background(), cs, metav1.CreateOptions{}) + defer func() { + err = crc.OperatorsV1alpha1().CatalogSources(cs.GetNamespace()).Delete(context.Background(), cs.GetName(), *deleteOpts) + Expect(err).ToNot(HaveOccurred()) + }() + + By(`Wait for the CatalogSource status to be updated to reflect its invalid spec`) + _, err = fetchCatalogSourceOnStatus(crc, cs.GetName(), cs.GetNamespace(), catalogSourceInvalidSpec) + Expect(err).ToNot(HaveOccurred(), "catalog source did not become ready") + + subName := genName("sub-") + cleanup := createSubscriptionForCatalog( + crc, + cs.GetNamespace(), + subName, + cs.GetName(), + testPackageName, + betaChannel, + "", + operatorsv1alpha1.ApprovalManual, + ) + defer cleanup() + + var sub *operatorsv1alpha1.Subscription + sub, err = fetchSubscription( + crc, + cs.GetNamespace(), + subName, + subscriptionHasCondition( + operatorsv1alpha1.SubscriptionCatalogSourcesUnhealthy, + corev1.ConditionTrue, + operatorsv1alpha1.UnhealthyCatalogSourceFound, + fmt.Sprintf("targeted catalogsource %s/%s unhealthy", cs.GetNamespace(), cs.GetName()), + ), + ) + Expect(err).NotTo(HaveOccurred()) + Expect(sub).ToNot(BeNil()) + }) + }) + + Context("is internal and its spec is missing the configmap reference", func() { + It("should surface catalog health", func() { + cs := &operatorsv1alpha1.CatalogSource{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.CatalogSourceKind, + APIVersion: operatorsv1alpha1.CatalogSourceCRDAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: genName("cs-"), + Namespace: generatedNamespace.GetName(), + }, + Spec: operatorsv1alpha1.CatalogSourceSpec{ + SourceType: operatorsv1alpha1.SourceTypeInternal, + GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ + SecurityContextConfig: operatorsv1alpha1.Restricted, + }, + }, + } + + var err error + cs, err = crc.OperatorsV1alpha1().CatalogSources(generatedNamespace.GetName()).Create(context.Background(), cs, metav1.CreateOptions{}) + defer func() { + err = crc.OperatorsV1alpha1().CatalogSources(cs.GetNamespace()).Delete(context.Background(), cs.GetName(), *deleteOpts) + Expect(err).ToNot(HaveOccurred()) + }() + + subName := genName("sub-") + cleanup := createSubscriptionForCatalog( + crc, + cs.GetNamespace(), + subName, + cs.GetName(), + testPackageName, + betaChannel, + "", + operatorsv1alpha1.ApprovalManual, + ) + defer cleanup() + + var sub *operatorsv1alpha1.Subscription + sub, err = fetchSubscription( + crc, + cs.GetNamespace(), + subName, + subscriptionHasCondition( + operatorsv1alpha1.SubscriptionCatalogSourcesUnhealthy, + corev1.ConditionTrue, + operatorsv1alpha1.UnhealthyCatalogSourceFound, + fmt.Sprintf("targeted catalogsource %s/%s unhealthy", cs.GetNamespace(), cs.GetName()), + ), + ) + Expect(err).NotTo(HaveOccurred()) + Expect(sub).ToNot(BeNil()) + }) + }) + + Context("is configmap and its spec is missing the configmap reference", func() { + It("should surface catalog health", func() { + cs := &operatorsv1alpha1.CatalogSource{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.CatalogSourceKind, + APIVersion: operatorsv1alpha1.CatalogSourceCRDAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: genName("cs-"), + Namespace: generatedNamespace.GetName(), + }, + Spec: operatorsv1alpha1.CatalogSourceSpec{ + SourceType: operatorsv1alpha1.SourceTypeInternal, + GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ + SecurityContextConfig: operatorsv1alpha1.Restricted, + }, + }, + } + + var err error + cs, err = crc.OperatorsV1alpha1().CatalogSources(generatedNamespace.GetName()).Create(context.Background(), cs, metav1.CreateOptions{}) + defer func() { + err = crc.OperatorsV1alpha1().CatalogSources(cs.GetNamespace()).Delete(context.Background(), cs.GetName(), *deleteOpts) + Expect(err).ToNot(HaveOccurred()) + }() + + subName := genName("sub-") + cleanup := createSubscriptionForCatalog( + crc, + cs.GetNamespace(), + subName, + cs.GetName(), + testPackageName, + betaChannel, + "", + operatorsv1alpha1.ApprovalAutomatic, + ) + defer cleanup() + + var sub *operatorsv1alpha1.Subscription + sub, err = fetchSubscription( + crc, + cs.GetNamespace(), + subName, + subscriptionHasCondition( + operatorsv1alpha1.SubscriptionCatalogSourcesUnhealthy, + corev1.ConditionTrue, + operatorsv1alpha1.UnhealthyCatalogSourceFound, + fmt.Sprintf("targeted catalogsource %s/%s unhealthy", cs.GetNamespace(), cs.GetName()), + ), + ) + Expect(err).NotTo(HaveOccurred()) + Expect(sub).ToNot(BeNil()) + }) + }) + }) + + }) + + It("can reconcile InstallPlan status", func() { + By(`TestSubscriptionInstallPlanStatus ensures that a Subscription has the appropriate status conditions for possible referenced InstallPlan states.`) + c := newKubeClient() + crc := newCRClient() + + By(`Create CatalogSource, cs, in ns`) + pkgName := genName("pkg-") + channelName := genName("channel-") + crd := newCRD(pkgName) + csv := newCSV(pkgName, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) + manifests := []registry.PackageManifest{ + { + PackageName: pkgName, + Channels: []registry.PackageChannel{ + {Name: channelName, CurrentCSVName: csv.GetName()}, + }, + DefaultChannelName: channelName, + }, + } + catalogName := genName("catalog-") + _, cleanupCatalogSource := createInternalCatalogSource(c, crc, catalogName, generatedNamespace.GetName(), manifests, []apiextensionsv1.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{csv}) + defer cleanupCatalogSource() + _, err := fetchCatalogSourceOnStatus(crc, catalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) + Expect(err).ToNot(HaveOccurred()) + + By(`Create Subscription to a package of cs in ns, sub`) + subName := genName("sub-") + defer createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subName, catalogName, pkgName, channelName, pkgName, operatorsv1alpha1.ApprovalAutomatic)() + + By(`Wait for the package from sub to install successfully with no remaining InstallPlan status conditions`) + checker := subscriptionStateAtLatestChecker() + sub, err := fetchSubscription(crc, generatedNamespace.GetName(), subName, func(s *operatorsv1alpha1.Subscription) bool { + for _, cond := range s.Status.Conditions { + switch cond.Type { + case operatorsv1alpha1.SubscriptionInstallPlanMissing, operatorsv1alpha1.SubscriptionInstallPlanPending, operatorsv1alpha1.SubscriptionInstallPlanFailed: + return false + } + } + return checker(s) + }) + Expect(err).ToNot(HaveOccurred()) + Expect(sub).ToNot(BeNil()) + + By(`Store conditions for later comparision`) + conds := sub.Status.Conditions + + ref := sub.Status.InstallPlanRef + Expect(ref).ToNot(BeNil()) + + By(`Get the InstallPlan`) + plan := &operatorsv1alpha1.InstallPlan{} + plan.SetNamespace(ref.Namespace) + plan.SetName(ref.Name) + + By(`Set the InstallPlan's approval mode to Manual`) + Eventually(Apply(plan, func(p *operatorsv1alpha1.InstallPlan) error { + p.Spec.Approval = operatorsv1alpha1.ApprovalManual + + p.Spec.Approved = false + return nil + })).Should(Succeed()) + + By(`Set the InstallPlan's phase to None`) + Eventually(Apply(plan, func(p *operatorsv1alpha1.InstallPlan) error { + p.Status.Phase = operatorsv1alpha1.InstallPlanPhaseNone + return nil + })).Should(Succeed()) + + By(`Wait for sub to have status condition SubscriptionInstallPlanPending true and reason InstallPlanNotYetReconciled`) + sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, func(s *operatorsv1alpha1.Subscription) bool { + cond := s.Status.GetCondition(operatorsv1alpha1.SubscriptionInstallPlanPending) + return cond.Status == corev1.ConditionTrue && cond.Reason == operatorsv1alpha1.InstallPlanNotYetReconciled + }) + Expect(err).ToNot(HaveOccurred()) + + By(`Set the phase to InstallPlanPhaseRequiresApproval`) + Eventually(Apply(plan, func(p *operatorsv1alpha1.InstallPlan) error { + p.Status.Phase = operatorsv1alpha1.InstallPlanPhaseRequiresApproval + return nil + })).Should(Succeed()) + + By(`Wait for sub to have status condition SubscriptionInstallPlanPending true and reason RequiresApproval`) + sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, func(s *operatorsv1alpha1.Subscription) bool { + cond := s.Status.GetCondition(operatorsv1alpha1.SubscriptionInstallPlanPending) + return cond.Status == corev1.ConditionTrue && cond.Reason == string(operatorsv1alpha1.InstallPlanPhaseRequiresApproval) + }) + Expect(err).ToNot(HaveOccurred()) + + By(`Set the phase to InstallPlanPhaseInstalling`) + Eventually(Apply(plan, func(p *operatorsv1alpha1.InstallPlan) error { + p.Status.Phase = operatorsv1alpha1.InstallPlanPhaseInstalling + return nil + })).Should(Succeed()) + + By(`Wait for sub to have status condition SubscriptionInstallPlanPending true and reason Installing`) + sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, func(s *operatorsv1alpha1.Subscription) bool { + cond := s.Status.GetCondition(operatorsv1alpha1.SubscriptionInstallPlanPending) + isConditionPresent := cond.Status == corev1.ConditionTrue && cond.Reason == string(operatorsv1alpha1.InstallPlanPhaseInstalling) + + if isConditionPresent { + return true + } + + // Sometimes the transition from installing to complete can be so quick that the test does not capture + // the condition in the subscription before it is removed. To mitigate this, we check if the installplan + // has transitioned to complete and exit out the fetch subscription loop if so. + // This is a mitigation. We should probably fix this test appropriately. + // issue: https://github.com/operator-framework/operator-lifecycle-manager/issues/2667 + ip, err := crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Get(context.TODO(), plan.Name, metav1.GetOptions{}) + if err != nil { + // retry on failure + return false + } + isInstallPlanComplete := ip.Status.Phase == operatorsv1alpha1.InstallPlanPhaseComplete + + return isInstallPlanComplete + }) + Expect(err).ToNot(HaveOccurred()) + + By(`Set the phase to InstallPlanPhaseFailed and remove all status conditions`) + Eventually(Apply(plan, func(p *operatorsv1alpha1.InstallPlan) error { + p.Status.Phase = operatorsv1alpha1.InstallPlanPhaseFailed + p.Status.Conditions = nil + return nil + })).Should(Succeed()) + + By(`Wait for sub to have status condition SubscriptionInstallPlanFailed true and reason InstallPlanFailed`) + sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, func(s *operatorsv1alpha1.Subscription) bool { + cond := s.Status.GetCondition(operatorsv1alpha1.SubscriptionInstallPlanFailed) + return cond.Status == corev1.ConditionTrue && cond.Reason == operatorsv1alpha1.InstallPlanFailed + }) + Expect(err).ToNot(HaveOccurred()) + + By(`Set status condition of type Installed to false with reason InstallComponentFailed`) + Eventually(Apply(plan, func(p *operatorsv1alpha1.InstallPlan) error { + p.Status.Phase = operatorsv1alpha1.InstallPlanPhaseFailed + failedCond := p.Status.GetCondition(operatorsv1alpha1.InstallPlanInstalled) + failedCond.Status = corev1.ConditionFalse + failedCond.Reason = operatorsv1alpha1.InstallPlanReasonComponentFailed + p.Status.SetCondition(failedCond) + return nil + })).Should(Succeed()) + + By(`Wait for sub to have status condition SubscriptionInstallPlanFailed true and reason InstallComponentFailed`) + sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, func(s *operatorsv1alpha1.Subscription) bool { + cond := s.Status.GetCondition(operatorsv1alpha1.SubscriptionInstallPlanFailed) + return cond.Status == corev1.ConditionTrue && cond.Reason == string(operatorsv1alpha1.InstallPlanReasonComponentFailed) + }) + Expect(err).ToNot(HaveOccurred()) + + By(`Delete the referenced InstallPlan`) + Eventually(func() error { + return crc.OperatorsV1alpha1().InstallPlans(ref.Namespace).Delete(context.Background(), ref.Name, metav1.DeleteOptions{}) + }).Should(Succeed()) + + By(`Wait for sub to have status condition SubscriptionInstallPlanMissing true`) + sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, func(s *operatorsv1alpha1.Subscription) bool { + return s.Status.GetCondition(operatorsv1alpha1.SubscriptionInstallPlanMissing).Status == corev1.ConditionTrue + }) + Expect(err).ToNot(HaveOccurred()) + Expect(sub).ToNot(BeNil()) + + By(`Ensure InstallPlan-related status conditions match what we're expecting`) + for _, cond := range conds { + switch condType := cond.Type; condType { + case operatorsv1alpha1.SubscriptionInstallPlanPending, operatorsv1alpha1.SubscriptionInstallPlanFailed: + require.FailNowf(GinkgoT(), "failed", "subscription contains unexpected installplan condition: %v", cond) + case operatorsv1alpha1.SubscriptionInstallPlanMissing: + require.Equal(GinkgoT(), operatorsv1alpha1.ReferencedInstallPlanNotFound, cond.Reason) + } + } + }) + + It("creation with pod config", func() { + + newConfigClient := func(t GinkgoTInterface) configv1client.ConfigV1Interface { + client, err := configv1client.NewForConfig(ctx.Ctx().RESTConfig()) + require.NoError(GinkgoT(), err) + + return client + } + + proxyEnvVarFunc := func(t GinkgoTInterface, client configv1client.ConfigV1Interface) []corev1.EnvVar { + if discovery.ServerSupportsVersion(ctx.Ctx().KubeClient().KubernetesInterface().Discovery(), configv1.GroupVersion) != nil { + return nil + } + + proxy, getErr := client.Proxies().Get(context.Background(), "cluster", metav1.GetOptions{}) + if apierrors.IsNotFound(getErr) { + return nil + } + require.NoError(GinkgoT(), getErr) + require.NotNil(GinkgoT(), proxy) + + proxyEnv := []corev1.EnvVar{} + + if proxy.Status.HTTPProxy != "" { + proxyEnv = append(proxyEnv, corev1.EnvVar{ + Name: "HTTP_PROXY", + Value: proxy.Status.HTTPProxy, + }) + } + + if proxy.Status.HTTPSProxy != "" { + proxyEnv = append(proxyEnv, corev1.EnvVar{ + Name: "HTTPS_PROXY", + Value: proxy.Status.HTTPSProxy, + }) + } + + if proxy.Status.NoProxy != "" { + proxyEnv = append(proxyEnv, corev1.EnvVar{ + Name: "NO_PROXY", + Value: proxy.Status.NoProxy, + }) + } + + return proxyEnv + } + + kubeClient := newKubeClient() + crClient := newCRClient() + config := newConfigClient(GinkgoT()) + + By(`Create a ConfigMap that is mounted to the operator via the subscription`) + testConfigMapName := genName("test-configmap-") + testConfigMap := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: testConfigMapName, + }, + } + + _, err := kubeClient.KubernetesInterface().CoreV1().ConfigMaps(generatedNamespace.GetName()).Create(context.Background(), testConfigMap, metav1.CreateOptions{}) + require.NoError(GinkgoT(), err) + defer func() { + err := kubeClient.KubernetesInterface().CoreV1().ConfigMaps(generatedNamespace.GetName()).Delete(context.Background(), testConfigMap.Name, metav1.DeleteOptions{}) + require.NoError(GinkgoT(), err) + }() + + By(`Configure the Subscription.`) + + podEnv := []corev1.EnvVar{ + { + Name: "MY_ENV_VARIABLE1", + Value: "value1", + }, + { + Name: "MY_ENV_VARIABLE2", + Value: "value2", + }, + } + testVolumeName := genName("test-volume-") + podVolumes := []corev1.Volume{ + { + Name: testVolumeName, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: testConfigMapName, + }, + }, + }, + }, + } + + podVolumeMounts := []corev1.VolumeMount{ + {Name: testVolumeName, MountPath: "/test"}, + } + + podTolerations := []corev1.Toleration{ + { + Key: "my-toleration-key", + Value: "my-toleration-value", + Effect: corev1.TaintEffectNoSchedule, + Operator: corev1.TolerationOpEqual, + }, + } + podResources := &corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + } + + podConfig := &operatorsv1alpha1.SubscriptionConfig{ + Env: podEnv, + Volumes: podVolumes, + VolumeMounts: podVolumeMounts, + Tolerations: podTolerations, + Resources: podResources, + } + + permissions := deploymentPermissions() + catsrc, subSpec, catsrcCleanup := newCatalogSource(GinkgoT(), kubeClient, crClient, "podconfig", generatedNamespace.GetName(), permissions) + defer catsrcCleanup() + + By(`Ensure that the catalog source is resolved before we create a subscription.`) + _, err = fetchCatalogSourceOnStatus(crClient, catsrc.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) + require.NoError(GinkgoT(), err) + + subscriptionName := genName("podconfig-sub-") + subSpec.Config = podConfig + cleanupSubscription := createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subSpec) + defer cleanupSubscription() + + subscription, err := fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionStateAtLatestChecker()) + require.NoError(GinkgoT(), err) + require.NotNil(GinkgoT(), subscription) + + proxyEnv := proxyEnvVarFunc(GinkgoT(), config) + expected := podEnv + expected = append(expected, proxyEnv...) + + Eventually(func() error { + csv, err := fetchCSV(crClient, generatedNamespace.GetName(), subscription.Status.CurrentCSV, buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded)) + if err != nil { + return err + } + + return checkDeploymentWithPodConfiguration(kubeClient, csv, podConfig.Env, podConfig.Volumes, podConfig.VolumeMounts, podConfig.Tolerations, podConfig.Resources) + }).Should(Succeed()) + }) + + It("creation with nodeSelector config", func() { + kubeClient := newKubeClient() + crClient := newCRClient() + + By(`Create a ConfigMap that is mounted to the operator via the subscription`) + testConfigMapName := genName("test-configmap-") + testConfigMap := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: testConfigMapName, + }, + } + + _, err := kubeClient.KubernetesInterface().CoreV1().ConfigMaps(generatedNamespace.GetName()).Create(context.Background(), testConfigMap, metav1.CreateOptions{}) + require.NoError(GinkgoT(), err) + defer func() { + err := kubeClient.KubernetesInterface().CoreV1().ConfigMaps(generatedNamespace.GetName()).Delete(context.Background(), testConfigMap.Name, metav1.DeleteOptions{}) + require.NoError(GinkgoT(), err) + }() + + By(`Configure the Subscription.`) + podNodeSelector := map[string]string{ + "foo": "bar", + } + + podConfig := &operatorsv1alpha1.SubscriptionConfig{ + NodeSelector: podNodeSelector, + } + + permissions := deploymentPermissions() + catsrc, subSpec, catsrcCleanup := newCatalogSource(GinkgoT(), kubeClient, crClient, "podconfig", generatedNamespace.GetName(), permissions) + defer catsrcCleanup() + + By(`Ensure that the catalog source is resolved before we create a subscription.`) + _, err = fetchCatalogSourceOnStatus(crClient, catsrc.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) + require.NoError(GinkgoT(), err) + + subscriptionName := genName("podconfig-sub-") + subSpec.Config = podConfig + cleanupSubscription := createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subSpec) + defer cleanupSubscription() + + subscription, err := fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionStateAtLatestChecker()) + require.NoError(GinkgoT(), err) + require.NotNil(GinkgoT(), subscription) + + csv, err := fetchCSV(crClient, generatedNamespace.GetName(), subscription.Status.CurrentCSV, buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseInstalling, operatorsv1alpha1.CSVPhaseSucceeded)) + require.NoError(GinkgoT(), err) + + Eventually(func() error { + return checkDeploymentHasPodConfigNodeSelector(GinkgoT(), kubeClient, csv, podNodeSelector) + }, timeout, interval).Should(Succeed()) + + }) + + It("[FLAKE] creation with dependencies", func() { + + kubeClient := newKubeClient() + crClient := newCRClient() + + permissions := deploymentPermissions() + + catsrc, subSpec, catsrcCleanup := newCatalogSourceWithDependencies(GinkgoT(), kubeClient, crClient, "podconfig", generatedNamespace.GetName(), permissions) + defer catsrcCleanup() + + By(`Ensure that the catalog source is resolved before we create a subscription.`) + _, err := fetchCatalogSourceOnStatus(crClient, catsrc.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) + require.NoError(GinkgoT(), err) + + By(`Create duplicates of the CatalogSource`) + for i := 0; i < 10; i++ { + duplicateCatsrc, _, duplicateCatSrcCleanup := newCatalogSourceWithDependencies(GinkgoT(), kubeClient, crClient, "podconfig", generatedNamespace.GetName(), permissions) + defer duplicateCatSrcCleanup() + + By(`Ensure that the catalog source is resolved before we create a subscription.`) + _, err = fetchCatalogSourceOnStatus(crClient, duplicateCatsrc.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) + require.NoError(GinkgoT(), err) + } + + By(`Create a subscription that has a dependency`) + subscriptionName := genName("podconfig-sub-") + cleanupSubscription := createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subSpec) + defer cleanupSubscription() + + subscription, err := fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionStateAtLatestChecker()) + require.NoError(GinkgoT(), err) + require.NotNil(GinkgoT(), subscription) + + By(`Check that a single catalog source was used to resolve the InstallPlan`) + installPlan, err := fetchInstallPlanWithNamespace(GinkgoT(), crClient, subscription.Status.InstallPlanRef.Name, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete)) + require.NoError(GinkgoT(), err) + require.Len(GinkgoT(), installPlan.Status.CatalogSources, 1) + }) + + It("creation with dependencies required and provided in different versions of an operator in the same package", func() { + By(` PARITY: this test covers the same scenario as the TestSolveOperators_PackageCannotSelfSatisfy unit test`) + kubeClient := ctx.Ctx().KubeClient() + crClient := ctx.Ctx().OperatorClient() + + crd := newCRD(genName("ins")) + crd2 := newCRD(genName("ins")) + + By(`csvs for catalogsource 1`) + csvs1 := make([]operatorsv1alpha1.ClusterServiceVersion, 0) + + By(`csvs for catalogsource 2`) + csvs2 := make([]operatorsv1alpha1.ClusterServiceVersion, 0) + + testPackage := registry.PackageManifest{PackageName: "test-package"} + By("Package A", func() { + Step(1, "Default Channel: Stable", func() { + testPackage.DefaultChannelName = stableChannel + }) + + Step(1, "Channel Stable", func() { + Step(2, "Operator A (Requires CRD, CRD 2)", func() { + csvA := newCSV("csv-a", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), nil, []apiextensionsv1.CustomResourceDefinition{crd, crd2}, nil) + testPackage. + Channels = append(testPackage. + Channels, registry.PackageChannel{Name: stableChannel, CurrentCSVName: csvA.GetName()}) + csvs1 = append(csvs1, csvA) + }) + }) + + Step(1, "Channel Alpha", func() { + Step(2, "Operator ABC (Provides: CRD, CRD 2)", func() { + csvABC := newCSV("csv-abc", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd, crd2}, nil, nil) + testPackage. + Channels = append(testPackage. + Channels, registry.PackageChannel{Name: alphaChannel, CurrentCSVName: csvABC.GetName()}) + csvs1 = append(csvs1, csvABC) + }) + }) + }) + + anotherPackage := registry.PackageManifest{PackageName: "another-package"} + By("Package B", func() { + Step(1, "Default Channel: Stable", func() { + anotherPackage.DefaultChannelName = stableChannel + }) + + Step(1, "Channel Stable", func() { + Step(2, "Operator B (Provides: CRD)", func() { + csvB := newCSV("csv-b", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) + anotherPackage.Channels = append(anotherPackage.Channels, registry.PackageChannel{Name: stableChannel, CurrentCSVName: csvB.GetName()}) + csvs1 = append(csvs1, csvB) + }) + }) + + Step(1, "Channel Alpha", func() { + Step(2, "Operator D (Provides: CRD)", func() { + csvD := newCSV("csv-d", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) + anotherPackage.Channels = append(anotherPackage.Channels, registry.PackageChannel{Name: alphaChannel, CurrentCSVName: csvD.GetName()}) + csvs1 = append(csvs1, csvD) + }) + }) + }) + + packageBInCatsrc2 := registry.PackageManifest{PackageName: "another-package"} + By("Package B", func() { + Step(1, "Default Channel: Stable", func() { + packageBInCatsrc2.DefaultChannelName = stableChannel + }) + + Step(1, "Channel Stable", func() { + Step(2, "Operator C (Provides: CRD 2)", func() { + csvC := newCSV("csv-c", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd2}, nil, nil) + packageBInCatsrc2.Channels = append(packageBInCatsrc2.Channels, registry.PackageChannel{Name: stableChannel, CurrentCSVName: csvC.GetName()}) + csvs2 = append(csvs2, csvC) + }) + }) + }) + + packageC := registry.PackageManifest{PackageName: "PackageC"} + By("Package C", func() { + Step(1, "Default Channel: Stable", func() { + packageC.DefaultChannelName = stableChannel + }) + + Step(1, "Channel Stable", func() { + Step(2, "Operator E (Provides: CRD 2)", func() { + csvE := newCSV("csv-e", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd2}, nil, nil) + packageC.Channels = append(packageC.Channels, registry.PackageChannel{Name: stable, CurrentCSVName: csvE.GetName()}) + csvs2 = append(csvs2, csvE) + }) + }) + }) + + By(`create catalogsources`) + var catsrc, catsrc2 *operatorsv1alpha1.CatalogSource + var cleanup cleanupFunc + By("creating catalogsources", func() { + var c1, c2 cleanupFunc + catsrc, c1 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc"), generatedNamespace.GetName(), []registry.PackageManifest{testPackage, anotherPackage}, []apiextensionsv1.CustomResourceDefinition{crd, crd2}, csvs1) + catsrc2, c2 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc2"), generatedNamespace.GetName(), []registry.PackageManifest{packageBInCatsrc2, packageC}, []apiextensionsv1.CustomResourceDefinition{crd, crd2}, csvs2) + cleanup = func() { + c1() + c2() + } + }) + defer cleanup() + + By("waiting for catalogsources to be ready", func() { + _, err := fetchCatalogSourceOnStatus(crClient, catsrc.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) + require.NoError(GinkgoT(), err) + _, err = fetchCatalogSourceOnStatus(crClient, catsrc2.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) + require.NoError(GinkgoT(), err) + }) + + By(`Create a subscription for test-package in catsrc`) + subscriptionSpec := &operatorsv1alpha1.SubscriptionSpec{ + CatalogSource: catsrc.GetName(), + CatalogSourceNamespace: catsrc.GetNamespace(), + Package: testPackage.PackageName, + Channel: stableChannel, + InstallPlanApproval: operatorsv1alpha1.ApprovalAutomatic, + } + subscriptionName := genName("sub-") + cleanupSubscription := createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subscriptionSpec) + defer cleanupSubscription() + + subscription, err := fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) + require.NoError(GinkgoT(), err) + require.NotNil(GinkgoT(), subscription) + + By(`ensure correct CSVs were picked`) + var got []string + Eventually(func() []string { + ip, err := crClient.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Get(context.Background(), subscription.Status.InstallPlanRef.Name, metav1.GetOptions{}) + if err != nil { + return nil + } + got = ip.Spec.ClusterServiceVersionNames + return got + }).ShouldNot(BeNil()) + require.ElementsMatch(GinkgoT(), []string{"csv-a", "csv-b", "csv-e"}, got) + }) + + Context("to an operator with dependencies from different CatalogSources with priorities", func() { + var ( + kubeClient operatorclient.ClientInterface + crClient versioned.Interface + crd apiextensionsv1.CustomResourceDefinition + packageMain, packageDepRight, packageDepWrong registry.PackageManifest + csvsMain, csvsRight, csvsWrong []operatorsv1alpha1.ClusterServiceVersion + catsrcMain, catsrcDepRight, catsrcDepWrong *operatorsv1alpha1.CatalogSource + cleanup, cleanupSubscription cleanupFunc + ) + const ( + mainCSVName = "csv-main" + rightCSVName = "csv-right" + wrongCSVName = "csv-wrong" + ) + + BeforeEach(func() { + kubeClient = ctx.Ctx().KubeClient() + crClient = ctx.Ctx().OperatorClient() + crd = newCRD(genName("ins")) + + packageMain = registry.PackageManifest{PackageName: genName("PkgMain-")} + csv := newCSV(mainCSVName, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), nil, + []apiextensionsv1.CustomResourceDefinition{crd}, nil) + packageMain.DefaultChannelName = stableChannel + packageMain.Channels = append(packageMain.Channels, registry.PackageChannel{Name: stableChannel, CurrentCSVName: csv.GetName()}) + + csvsMain = []operatorsv1alpha1.ClusterServiceVersion{csv} + csvsRight = []operatorsv1alpha1.ClusterServiceVersion{} + csvsWrong = []operatorsv1alpha1.ClusterServiceVersion{} + }) + + Context("creating CatalogSources providing the same dependency with different names", func() { + var catsrcCleanup1, catsrcCleanup2, catsrcCleanup3 cleanupFunc + + BeforeEach(func() { + packageDepRight = registry.PackageManifest{PackageName: "PackageDependent"} + csv := newCSV(rightCSVName, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), + []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) + packageDepRight.DefaultChannelName = alphaChannel + packageDepRight.Channels = []registry.PackageChannel{{Name: alphaChannel, CurrentCSVName: csv.GetName()}} + csvsRight = append(csvsRight, csv) + + csv.Name = wrongCSVName + packageDepWrong = packageDepRight + packageDepWrong.Channels = []registry.PackageChannel{{Name: alphaChannel, CurrentCSVName: csv.GetName()}} + csvsWrong = append(csvsWrong, csv) + + catsrcMain, catsrcCleanup1 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc"), generatedNamespace.GetName(), + []registry.PackageManifest{packageMain}, nil, csvsMain) + + catsrcDepRight, catsrcCleanup2 = createInternalCatalogSource(kubeClient, crClient, "catsrc1", generatedNamespace.GetName(), + []registry.PackageManifest{packageDepRight}, []apiextensionsv1.CustomResourceDefinition{crd}, csvsRight) + + catsrcDepWrong, catsrcCleanup3 = createInternalCatalogSource(kubeClient, crClient, "catsrc2", generatedNamespace.GetName(), + []registry.PackageManifest{packageDepWrong}, []apiextensionsv1.CustomResourceDefinition{crd}, csvsWrong) + + _, err := fetchCatalogSourceOnStatus(crClient, catsrcMain.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) + Expect(err).ToNot(HaveOccurred()) + _, err = fetchCatalogSourceOnStatus(crClient, catsrcDepRight.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) + Expect(err).ToNot(HaveOccurred()) + _, err = fetchCatalogSourceOnStatus(crClient, catsrcDepWrong.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) + Expect(err).ToNot(HaveOccurred()) + }) + + AfterEach(func() { + if catsrcCleanup1 != nil { + catsrcCleanup1() + } + if catsrcCleanup2 != nil { + catsrcCleanup2() + } + if catsrcCleanup3 != nil { + catsrcCleanup3() + } + }) + + When("creating subscription for the main package", func() { + var subscription *operatorsv1alpha1.Subscription + + BeforeEach(func() { + By(`Create a subscription for test-package in catsrc`) + subscriptionSpec := &operatorsv1alpha1.SubscriptionSpec{ + CatalogSource: catsrcMain.GetName(), + CatalogSourceNamespace: catsrcMain.GetNamespace(), + Package: packageMain.PackageName, + Channel: stableChannel, + InstallPlanApproval: operatorsv1alpha1.ApprovalAutomatic, + } + subscriptionName := genName("sub-") + cleanupSubscription = createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subscriptionSpec) + + var err error + subscription, err = fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) + Expect(err).ToNot(HaveOccurred()) + Expect(subscription).ToNot(BeNil()) + + _, err = fetchCSV(crClient, generatedNamespace.GetName(), mainCSVName, csvSucceededChecker) + Expect(err).ToNot(HaveOccurred()) + + }) + + AfterEach(func() { + if cleanupSubscription != nil { + cleanupSubscription() + } + if cleanup != nil { + cleanup() + } + }) + + It("[FLAKE] choose the dependency from the right CatalogSource based on lexicographical name ordering of catalogs", func() { + By(`ensure correct CSVs were picked`) + Eventually(func() ([]string, error) { + ip, err := crClient.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Get(context.Background(), subscription.Status.InstallPlanRef.Name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + return ip.Spec.ClusterServiceVersionNames, nil + }).Should(ConsistOf(mainCSVName, rightCSVName)) + }) + }) + }) + + Context("creating the main and an arbitrary CatalogSources providing the same dependency", func() { + var catsrcCleanup1, catsrcCleanup2 cleanupFunc + + BeforeEach(func() { + + packageDepRight = registry.PackageManifest{PackageName: "PackageDependent"} + csv := newCSV(rightCSVName, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), + []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) + packageDepRight.DefaultChannelName = alphaChannel + packageDepRight.Channels = []registry.PackageChannel{{Name: alphaChannel, CurrentCSVName: csv.GetName()}} + csvsMain = append(csvsMain, csv) + + csv.Name = wrongCSVName + packageDepWrong = packageDepRight + packageDepWrong.Channels = []registry.PackageChannel{{Name: alphaChannel, CurrentCSVName: csv.GetName()}} + csvsWrong = append(csvsWrong, csv) + + catsrcMain, catsrcCleanup1 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc"), generatedNamespace.GetName(), + []registry.PackageManifest{packageDepRight, packageMain}, []apiextensionsv1.CustomResourceDefinition{crd}, csvsMain) + + catsrcDepWrong, catsrcCleanup2 = createInternalCatalogSourceWithPriority(kubeClient, crClient, + genName("catsrc"), generatedNamespace.GetName(), []registry.PackageManifest{packageDepWrong}, []apiextensionsv1.CustomResourceDefinition{crd}, + csvsWrong, 100) + + By(`waiting for catalogsources to be ready`) + _, err := fetchCatalogSourceOnStatus(crClient, catsrcMain.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) + Expect(err).ToNot(HaveOccurred()) + _, err = fetchCatalogSourceOnStatus(crClient, catsrcDepWrong.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) + Expect(err).ToNot(HaveOccurred()) + }) + + AfterEach(func() { + if catsrcCleanup1 != nil { + catsrcCleanup1() + } + if catsrcCleanup2 != nil { + catsrcCleanup2() + } + + }) + + When("creating subscription for the main package", func() { + var subscription *operatorsv1alpha1.Subscription + + BeforeEach(func() { + By(`Create a subscription for test-package in catsrc`) + subscriptionSpec := &operatorsv1alpha1.SubscriptionSpec{ + CatalogSource: catsrcMain.GetName(), + CatalogSourceNamespace: catsrcMain.GetNamespace(), + Package: packageMain.PackageName, + Channel: stableChannel, + InstallPlanApproval: operatorsv1alpha1.ApprovalAutomatic, + } + subscriptionName := genName("sub-") + cleanupSubscription = createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subscriptionSpec) + + var err error + subscription, err = fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) + Expect(err).ToNot(HaveOccurred()) + Expect(subscription).ToNot(BeNil()) + + _, err = fetchCSV(crClient, generatedNamespace.GetName(), mainCSVName, csvSucceededChecker) + Expect(err).ToNot(HaveOccurred()) + + }) + + AfterEach(func() { + if cleanupSubscription != nil { + cleanupSubscription() + } + if cleanup != nil { + cleanup() + } + }) + + It("choose the dependent package from the same catsrc as the installing operator", func() { + By(`ensure correct CSVs were picked`) + Eventually(func() ([]string, error) { + ip, err := crClient.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Get(context.Background(), subscription.Status.InstallPlanRef.Name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + return ip.Spec.ClusterServiceVersionNames, nil + }).Should(ConsistOf(mainCSVName, rightCSVName)) + }) + }) + }) + + Context("creating CatalogSources providing the same dependency with different priority value", func() { + var catsrcCleanup1, catsrcCleanup2, catsrcCleanup3 cleanupFunc + + BeforeEach(func() { + packageDepRight = registry.PackageManifest{PackageName: "PackageDependent"} + csv := newCSV(rightCSVName, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), + []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) + packageDepRight.DefaultChannelName = alphaChannel + packageDepRight.Channels = []registry.PackageChannel{{Name: alphaChannel, CurrentCSVName: csv.GetName()}} + csvsRight = append(csvsRight, csv) + + csv.Name = wrongCSVName + packageDepWrong = packageDepRight + packageDepWrong.Channels = []registry.PackageChannel{{Name: alphaChannel, CurrentCSVName: csv.GetName()}} + csvsWrong = append(csvsWrong, csv) + + catsrcMain, catsrcCleanup1 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc"), generatedNamespace.GetName(), + []registry.PackageManifest{packageMain}, nil, csvsMain) + + catsrcDepRight, catsrcCleanup2 = createInternalCatalogSourceWithPriority(kubeClient, crClient, + genName("catsrc"), generatedNamespace.GetName(), []registry.PackageManifest{packageDepRight}, []apiextensionsv1.CustomResourceDefinition{crd}, + csvsRight, 100) + + catsrcDepWrong, catsrcCleanup3 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc"), generatedNamespace.GetName(), + []registry.PackageManifest{packageDepWrong}, []apiextensionsv1.CustomResourceDefinition{crd}, csvsWrong) + + By(`waiting for catalogsources to be ready`) + _, err := fetchCatalogSourceOnStatus(crClient, catsrcMain.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) + Expect(err).ToNot(HaveOccurred()) + _, err = fetchCatalogSourceOnStatus(crClient, catsrcDepRight.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) + Expect(err).ToNot(HaveOccurred()) + _, err = fetchCatalogSourceOnStatus(crClient, catsrcDepWrong.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) + Expect(err).ToNot(HaveOccurred()) + }) + + AfterEach(func() { + if catsrcCleanup1 != nil { + catsrcCleanup1() + } + if catsrcCleanup2 != nil { + catsrcCleanup2() + } + if catsrcCleanup3 != nil { + catsrcCleanup3() + } + }) + + When("creating subscription for the main package", func() { + var subscription *operatorsv1alpha1.Subscription + + BeforeEach(func() { + By(`Create a subscription for test-package in catsrc`) + subscriptionSpec := &operatorsv1alpha1.SubscriptionSpec{ + CatalogSource: catsrcMain.GetName(), + CatalogSourceNamespace: catsrcMain.GetNamespace(), + Package: packageMain.PackageName, + Channel: stableChannel, + InstallPlanApproval: operatorsv1alpha1.ApprovalAutomatic, + } + subscriptionName := genName("sub-") + cleanupSubscription = createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subscriptionSpec) + + var err error + subscription, err = fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) + Expect(err).ToNot(HaveOccurred()) + Expect(subscription).ToNot(BeNil()) + + _, err = fetchCSV(crClient, generatedNamespace.GetName(), mainCSVName, csvSucceededChecker) + Expect(err).ToNot(HaveOccurred()) + + }) + + AfterEach(func() { + if cleanupSubscription != nil { + cleanupSubscription() + } + if cleanup != nil { + cleanup() + } + }) + + It("choose the dependent package from the catsrc with higher priority", func() { + By(`ensure correct CSVs were picked`) + Eventually(func() ([]string, error) { + ip, err := crClient.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Get(context.Background(), subscription.Status.InstallPlanRef.Name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + return ip.Spec.ClusterServiceVersionNames, nil + }).Should(ConsistOf(mainCSVName, rightCSVName)) + }) + }) + }) + + Context("creating CatalogSources providing the same dependency under test and global namespaces", func() { + var catsrcCleanup1, catsrcCleanup2, catsrcCleanup3 cleanupFunc + + BeforeEach(func() { + + packageDepRight = registry.PackageManifest{PackageName: "PackageDependent"} + csv := newCSV(rightCSVName, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), + []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) + packageDepRight.DefaultChannelName = alphaChannel + packageDepRight.Channels = []registry.PackageChannel{{Name: alphaChannel, CurrentCSVName: csv.GetName()}} + csvsRight = append(csvsRight, csv) + + csv.Name = wrongCSVName + packageDepWrong = packageDepRight + packageDepWrong.Channels = []registry.PackageChannel{{Name: alphaChannel, CurrentCSVName: csv.GetName()}} + csvsWrong = append(csvsWrong, csv) + + catsrcMain, catsrcCleanup1 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc"), generatedNamespace.GetName(), + []registry.PackageManifest{packageMain}, nil, csvsMain) + + catsrcDepRight, catsrcCleanup2 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc"), generatedNamespace.GetName(), + []registry.PackageManifest{packageDepRight}, []apiextensionsv1.CustomResourceDefinition{crd}, csvsRight) + + catsrcDepWrong, catsrcCleanup3 = createInternalCatalogSource(kubeClient, crClient, genName("catsrc"), operatorNamespace, + []registry.PackageManifest{packageDepWrong}, []apiextensionsv1.CustomResourceDefinition{crd}, csvsWrong) + + By(`waiting for catalogsources to be ready`) + _, err := fetchCatalogSourceOnStatus(crClient, catsrcMain.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) + Expect(err).ToNot(HaveOccurred()) + _, err = fetchCatalogSourceOnStatus(crClient, catsrcDepRight.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) + Expect(err).ToNot(HaveOccurred()) + _, err = fetchCatalogSourceOnStatus(crClient, catsrcDepWrong.GetName(), operatorNamespace, catalogSourceRegistryPodSynced()) + Expect(err).ToNot(HaveOccurred()) + }) + + AfterEach(func() { + if catsrcCleanup1 != nil { + catsrcCleanup1() + } + if catsrcCleanup2 != nil { + catsrcCleanup2() + } + if catsrcCleanup3 != nil { + catsrcCleanup3() + } + }) + + When("creating subscription for the main package", func() { + var subscription *operatorsv1alpha1.Subscription + + BeforeEach(func() { + By(`Create a subscription for test-package in catsrc`) + subscriptionSpec := &operatorsv1alpha1.SubscriptionSpec{ + CatalogSource: catsrcMain.GetName(), + CatalogSourceNamespace: catsrcMain.GetNamespace(), + Package: packageMain.PackageName, + Channel: stableChannel, + InstallPlanApproval: operatorsv1alpha1.ApprovalAutomatic, + } + subscriptionName := genName("sub-") + cleanupSubscription = createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subscriptionSpec) + + var err error + subscription, err = fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) + Expect(err).ToNot(HaveOccurred()) + Expect(subscription).ToNot(BeNil()) + + _, err = fetchCSV(crClient, generatedNamespace.GetName(), mainCSVName, csvSucceededChecker) + Expect(err).ToNot(HaveOccurred()) + + }) + + AfterEach(func() { + if cleanupSubscription != nil { + cleanupSubscription() + } + if cleanup != nil { + cleanup() + } + }) + + It("choose the dependent package from the catsrc in the same namespace as the installing operator", func() { + By(`ensure correct CSVs were picked`) + Eventually(func() ([]string, error) { + ip, err := crClient.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Get(context.Background(), subscription.Status.InstallPlanRef.Name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + return ip.Spec.ClusterServiceVersionNames, nil + }).Should(ConsistOf(mainCSVName, rightCSVName)) + }) + }) + }) + }) + + It("creation in case of transferring providedAPIs", func() { + By(`csvA owns CRD1 & csvB owns CRD2 and requires CRD1`) + By(`Create subscription for csvB lead to installation of csvB and csvA`) + By(`Update catsrc to upgrade csvA to csvNewA which now requires CRD1`) + By(`csvNewA can't be installed due to no other operators provide CRD1 for it`) + By(`(Note: OLM can't pick csvA as dependency for csvNewA as it is from the same`) + By(`same package)`) + By(`Update catsrc again to upgrade csvB to csvNewB which now owns both CRD1 and`) + By(`CRD2.`) + By(`Now csvNewA and csvNewB are installed successfully as csvNewB provides CRD1`) + By(`that csvNewA requires`) + By(` PARITY: this test covers the same scenario as the TestSolveOperators_TransferApiOwnership unit test`) + kubeClient := ctx.Ctx().KubeClient() + crClient := ctx.Ctx().OperatorClient() + + crd := newCRD(genName("ins")) + crd2 := newCRD(genName("ins")) + + By(`Create CSV`) + packageName1 := genName("apackage") + packageName2 := genName("bpackage") + + By(`csvA provides CRD`) + csvA := newCSV("nginx-a", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) + By(`csvB provides CRD2 and requires CRD`) + csvB := newCSV("nginx-b", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd2}, []apiextensionsv1.CustomResourceDefinition{crd}, nil) + By(`New csvA requires CRD (transfer CRD ownership to the new csvB)`) + csvNewA := newCSV("nginx-new-a", generatedNamespace.GetName(), "nginx-a", semver.MustParse("0.2.0"), nil, []apiextensionsv1.CustomResourceDefinition{crd}, nil) + By(`New csvB provides CRD and CRD2`) + csvNewB := newCSV("nginx-new-b", generatedNamespace.GetName(), "nginx-b", semver.MustParse("0.2.0"), []apiextensionsv1.CustomResourceDefinition{crd, crd2}, nil, nil) + + By(`constraints not satisfiable:`) + By(`apackagert6cq requires at least one of catsrcc6xgr/operators/stable/nginx-new-a,`) + By(`apackagert6cq is mandatory,`) + By(`pkgunique/apackagert6cq permits at most 1 of catsrcc6xgr/operators/stable/nginx-new-a, catsrcc6xgr/operators/stable/nginx-a,`) + By(`catsrcc6xgr/operators/stable/nginx-new-a requires at least one of catsrcc6xgr/operators/stable/nginx-a`) + + By(`Create PackageManifests 1`) + By(`Contain csvA, ABC and B`) + manifests := []registry.PackageManifest{ + { + PackageName: packageName1, + Channels: []registry.PackageChannel{ + {Name: stableChannel, CurrentCSVName: csvA.GetName()}, + }, + DefaultChannelName: stableChannel, + }, + { + PackageName: packageName2, + Channels: []registry.PackageChannel{ + {Name: stableChannel, CurrentCSVName: csvB.GetName()}, + }, + DefaultChannelName: stableChannel, + }, + } + + catalogSourceName := genName("catsrc") + catsrc, cleanup := createInternalCatalogSource(kubeClient, crClient, catalogSourceName, generatedNamespace.GetName(), manifests, []apiextensionsv1.CustomResourceDefinition{crd, crd2}, []operatorsv1alpha1.ClusterServiceVersion{csvA, csvB}) + defer cleanup() + + By(`Ensure that the catalog source is resolved before we create a subscription.`) + _, err := fetchCatalogSourceOnStatus(crClient, catsrc.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) + require.NoError(GinkgoT(), err) + + subscriptionSpec := &operatorsv1alpha1.SubscriptionSpec{ + CatalogSource: catsrc.GetName(), + CatalogSourceNamespace: catsrc.GetNamespace(), + Package: packageName2, + Channel: stableChannel, + StartingCSV: csvB.GetName(), + InstallPlanApproval: operatorsv1alpha1.ApprovalAutomatic, + } + + By(`Create a subscription that has a dependency`) + subscriptionName := genName("sub-") + cleanupSubscription := createSubscriptionForCatalogWithSpec(GinkgoT(), crClient, generatedNamespace.GetName(), subscriptionName, subscriptionSpec) + defer cleanupSubscription() + + subscription, err := fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionStateAtLatestChecker()) + require.NoError(GinkgoT(), err) + require.NotNil(GinkgoT(), subscription) + + By(`Check that a single catalog source was used to resolve the InstallPlan`) + _, err = fetchInstallPlanWithNamespace(GinkgoT(), crClient, subscription.Status.InstallPlanRef.Name, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete)) + require.NoError(GinkgoT(), err) + By(`Fetch CSVs A and B`) + _, err = fetchCSV(crClient, generatedNamespace.GetName(), csvA.Name, csvSucceededChecker) + require.NoError(GinkgoT(), err) + _, err = fetchCSV(crClient, generatedNamespace.GetName(), csvB.Name, csvSucceededChecker) + require.NoError(GinkgoT(), err) + + By(`Update PackageManifest`) + manifests = []registry.PackageManifest{ + { + PackageName: packageName1, + Channels: []registry.PackageChannel{ + {Name: stableChannel, CurrentCSVName: csvNewA.GetName()}, + }, + DefaultChannelName: stableChannel, + }, + { + PackageName: packageName2, + Channels: []registry.PackageChannel{ + {Name: stableChannel, CurrentCSVName: csvB.GetName()}, + }, + DefaultChannelName: stableChannel, + }, + } + updateInternalCatalog(GinkgoT(), kubeClient, crClient, catalogSourceName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{crd, crd2}, []operatorsv1alpha1.ClusterServiceVersion{csvNewA, csvA, csvB}, manifests) + csvAsub := strings.Join([]string{packageName1, stableChannel, catalogSourceName, generatedNamespace.GetName()}, "-") + _, err = fetchSubscription(crClient, generatedNamespace.GetName(), csvAsub, subscriptionStateAtLatestChecker()) + require.NoError(GinkgoT(), err) + By(`Ensure csvNewA is not installed`) + _, err = crClient.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Get(context.Background(), csvNewA.Name, metav1.GetOptions{}) + require.Error(GinkgoT(), err) + By(`Ensure csvA still exists`) + _, err = fetchCSV(crClient, generatedNamespace.GetName(), csvA.Name, csvSucceededChecker) + require.NoError(GinkgoT(), err) + + By(`Update packagemanifest again`) + manifests = []registry.PackageManifest{ + { + PackageName: packageName1, + Channels: []registry.PackageChannel{ + {Name: stableChannel, CurrentCSVName: csvNewA.GetName()}, + }, + DefaultChannelName: stableChannel, + }, + { + PackageName: packageName2, + Channels: []registry.PackageChannel{ + {Name: stableChannel, CurrentCSVName: csvNewB.GetName()}, + }, + DefaultChannelName: stableChannel, + }, + } + updateInternalCatalog(GinkgoT(), kubeClient, crClient, catalogSourceName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{crd, crd2}, []operatorsv1alpha1.ClusterServiceVersion{csvA, csvB, csvNewA, csvNewB}, manifests) + + _, err = fetchSubscription(crClient, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanDifferentChecker(subscription.Status.InstallPlanRef.Name)) + require.NoError(GinkgoT(), err) + By(`Ensure csvNewA is installed`) + _, err = fetchCSV(crClient, generatedNamespace.GetName(), csvNewA.Name, csvSucceededChecker) + require.NoError(GinkgoT(), err) + By(`Ensure csvNewB is installed`) + _, err = fetchCSV(crClient, generatedNamespace.GetName(), csvNewB.Name, csvSucceededChecker) + require.NoError(GinkgoT(), err) + }) + + When("A subscription is created for an operator that requires an API that is not available", func() { + var ( + c operatorclient.ClientInterface + crc versioned.Interface + teardown func() + cleanup func() + packages []registry.PackageManifest + crd = newCRD(genName("foo-")) + csvA operatorsv1alpha1.ClusterServiceVersion + csvB operatorsv1alpha1.ClusterServiceVersion + subName = genName("test-subscription-") + catSrcName = genName("test-catalog-") + ) + + BeforeEach(func() { + c = newKubeClient() + crc = newCRClient() + + packages = []registry.PackageManifest{ + { + PackageName: "test-package", + Channels: []registry.PackageChannel{ + {Name: "alpha", CurrentCSVName: "csvA"}, + }, + DefaultChannelName: "alpha", + }, + } + csvA = newCSV("csvA", generatedNamespace.GetName(), "", semver.MustParse("1.0.0"), nil, []apiextensionsv1.CustomResourceDefinition{crd}, nil) + + _, teardown = createInternalCatalogSource(c, ctx.Ctx().OperatorClient(), catSrcName, generatedNamespace.GetName(), packages, nil, []operatorsv1alpha1.ClusterServiceVersion{csvA}) + + By(`Ensure that the catalog source is resolved before we create a subscription.`) + _, err := fetchCatalogSourceOnStatus(crc, catSrcName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) + require.NoError(GinkgoT(), err) + + cleanup = createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subName, catSrcName, "test-package", "alpha", "", operatorsv1alpha1.ApprovalAutomatic) + }) + + AfterEach(func() { + cleanup() + teardown() + }) + + It("the subscription has a condition in it's status that indicates the resolution error", func() { + Eventually(func() (corev1.ConditionStatus, error) { + sub, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), subName, metav1.GetOptions{}) + if err != nil { + return corev1.ConditionUnknown, err + } + return sub.Status.GetCondition(operatorsv1alpha1.SubscriptionResolutionFailed).Status, nil + }).Should(Equal(corev1.ConditionTrue)) + }) + + When("the required API is made available", func() { + + BeforeEach(func() { + newPkg := registry.PackageManifest{ + PackageName: "another-package", + Channels: []registry.PackageChannel{ + {Name: "alpha", CurrentCSVName: "csvB"}, + }, + DefaultChannelName: "alpha", + } + packages = append(packages, newPkg) + + csvB = newCSV("csvB", generatedNamespace.GetName(), "", semver.MustParse("1.0.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) + + updateInternalCatalog(GinkgoT(), c, crc, catSrcName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{csvA, csvB}, packages) + }) + + It("the ResolutionFailed condition previously set in its status that indicated the resolution error is cleared off", func() { + Eventually(func() (corev1.ConditionStatus, error) { + sub, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), subName, metav1.GetOptions{}) + if err != nil { + return corev1.ConditionFalse, err + } + return sub.Status.GetCondition(operatorsv1alpha1.SubscriptionResolutionFailed).Status, nil + }).Should(Equal(corev1.ConditionUnknown)) + }) + }) + }) + + When("an unannotated ClusterServiceVersion exists with an associated Subscription", func() { + var ( + teardown func() + ) + + BeforeEach(func() { + teardown = func() {} + + packages := []registry.PackageManifest{ + { + PackageName: "package", + Channels: []registry.PackageChannel{ + {Name: "channel-x", CurrentCSVName: "csv-x"}, + {Name: "channel-y", CurrentCSVName: "csv-y"}, + }, + DefaultChannelName: "channel-x", + }, + } + + x := newCSV("csv-x", generatedNamespace.GetName(), "", semver.MustParse("1.0.0"), nil, nil, nil) + y := newCSV("csv-y", generatedNamespace.GetName(), "", semver.MustParse("1.0.0"), nil, nil, nil) + + _, teardown = createInternalCatalogSource(ctx.Ctx().KubeClient(), ctx.Ctx().OperatorClient(), "test-catalog", generatedNamespace.GetName(), packages, nil, []operatorsv1alpha1.ClusterServiceVersion{x, y}) + + createSubscriptionForCatalog(ctx.Ctx().OperatorClient(), generatedNamespace.GetName(), "test-subscription-x", "test-catalog", "package", "channel-x", "", operatorsv1alpha1.ApprovalAutomatic) + + Eventually(func() error { + var unannotated operatorsv1alpha1.ClusterServiceVersion + if err := ctx.Ctx().Client().Get(context.Background(), client.ObjectKey{Namespace: generatedNamespace.GetName(), Name: "csv-x"}, &unannotated); err != nil { + return err + } + if _, ok := unannotated.Annotations["operatorframework.io/properties"]; !ok { + return nil + } + delete(unannotated.Annotations, "operatorframework.io/properties") + return ctx.Ctx().Client().Update(context.Background(), &unannotated) + }).Should(Succeed()) + }) + + AfterEach(func() { + teardown() + }) + + It("uses inferred properties to prevent a duplicate installation from the same package ", func() { + createSubscriptionForCatalog(ctx.Ctx().OperatorClient(), generatedNamespace.GetName(), "test-subscription-y", "test-catalog", "package", "channel-y", "", operatorsv1alpha1.ApprovalAutomatic) + + Consistently(func() error { + var no operatorsv1alpha1.ClusterServiceVersion + return ctx.Ctx().Client().Get(context.Background(), client.ObjectKey{Namespace: generatedNamespace.GetName(), Name: "csv-y"}, &no) + }).ShouldNot(Succeed()) + }) + }) + + When("there exists a Subscription to an operator having dependency candidates in both default and nondefault channels", func() { + var ( + teardown func() + ) + + BeforeEach(func() { + teardown = func() {} + + packages := []registry.PackageManifest{ + { + PackageName: "dependency", + Channels: []registry.PackageChannel{ + {Name: "default", CurrentCSVName: "csv-dependency"}, + {Name: "nondefault", CurrentCSVName: "csv-dependency"}, + }, + DefaultChannelName: "default", + }, + { + PackageName: "root", + Channels: []registry.PackageChannel{ + {Name: "unimportant", CurrentCSVName: "csv-root"}, + }, + DefaultChannelName: "unimportant", + }, + } + + crds := []apiextensionsv1.CustomResourceDefinition{newCRD(genName("crd-"))} + csvs := []operatorsv1alpha1.ClusterServiceVersion{ + newCSV("csv-dependency", generatedNamespace.GetName(), "", semver.MustParse("1.0.0"), crds, nil, nil), + newCSV("csv-root", generatedNamespace.GetName(), "", semver.MustParse("1.0.0"), nil, crds, nil), + } + + _, teardown = createInternalCatalogSource(ctx.Ctx().KubeClient(), ctx.Ctx().OperatorClient(), "test-catalog", generatedNamespace.GetName(), packages, crds, csvs) + + createSubscriptionForCatalog(ctx.Ctx().OperatorClient(), generatedNamespace.GetName(), "test-subscription", "test-catalog", "root", "unimportant", "", operatorsv1alpha1.ApprovalAutomatic) + }) + + AfterEach(func() { + teardown() + }) + + It("should create a Subscription using the candidate's default channel", func() { + Eventually(func() ([]operatorsv1alpha1.Subscription, error) { + var list operatorsv1alpha1.SubscriptionList + if err := ctx.Ctx().Client().List(context.Background(), &list); err != nil { + return nil, err + } + return list.Items, nil + }).Should(ContainElement(WithTransform( + func(in operatorsv1alpha1.Subscription) operatorsv1alpha1.SubscriptionSpec { + return operatorsv1alpha1.SubscriptionSpec{ + CatalogSource: in.Spec.CatalogSource, + CatalogSourceNamespace: in.Spec.CatalogSourceNamespace, + Package: in.Spec.Package, + Channel: in.Spec.Channel, + } + }, + Equal(operatorsv1alpha1.SubscriptionSpec{ + CatalogSource: "test-catalog", + CatalogSourceNamespace: generatedNamespace.GetName(), + Package: "dependency", + Channel: "default", + }), + ))) + }) + }) + + It("unpacks bundle image", func() { + catsrc := &operatorsv1alpha1.CatalogSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: genName("kiali-"), + Namespace: generatedNamespace.GetName(), + Labels: map[string]string{"olm.catalogSource": "kaili-catalog"}, + }, + Spec: operatorsv1alpha1.CatalogSourceSpec{ + Image: "quay.io/operator-framework/ci-index:latest", + SourceType: operatorsv1alpha1.SourceTypeGrpc, + GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ + SecurityContextConfig: operatorsv1alpha1.Restricted, + }, + }, + } + catsrc, err := crc.OperatorsV1alpha1().CatalogSources(catsrc.GetNamespace()).Create(context.Background(), catsrc, metav1.CreateOptions{}) + require.NoError(GinkgoT(), err) + defer func() { + Eventually(func() error { + return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), catsrc)) + }).Should(Succeed()) + }() + + By("waiting for the CatalogSource to be ready") + catsrc, err = fetchCatalogSourceOnStatus(crc, catsrc.GetName(), catsrc.GetNamespace(), catalogSourceRegistryPodSynced()) + require.NoError(GinkgoT(), err) + + By("generating a Subscription") + subName := genName("kiali-") + cleanUpSubscriptionFn := createSubscriptionForCatalog(crc, catsrc.GetNamespace(), subName, catsrc.GetName(), "kiali", stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) + defer cleanUpSubscriptionFn() + + By("waiting for the InstallPlan to get created for the subscription") + sub, err := fetchSubscription(crc, catsrc.GetNamespace(), subName, subscriptionHasInstallPlanChecker()) + require.NoError(GinkgoT(), err) + + By("waiting for the expected InstallPlan's execution to either fail or succeed") + ipName := sub.Status.InstallPlanRef.Name + ip, err := waitForInstallPlan(crc, ipName, sub.GetNamespace(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseFailed, operatorsv1alpha1.InstallPlanPhaseComplete)) + require.NoError(GinkgoT(), err) + require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, ip.Status.Phase, "InstallPlan not complete") + + By("ensuring the InstallPlan contains the steps resolved from the bundle image") + operatorName := "kiali-operator" + expectedSteps := map[registry.ResourceKey]struct{}{ + {Name: operatorName, Kind: "ClusterServiceVersion"}: {}, + {Name: "kialis.kiali.io", Kind: "CustomResourceDefinition"}: {}, + {Name: "monitoringdashboards.monitoring.kiali.io", Kind: "CustomResourceDefinition"}: {}, + {Name: operatorName, Kind: "ServiceAccount"}: {}, + {Name: operatorName, Kind: "ClusterRole"}: {}, + {Name: operatorName, Kind: "ClusterRoleBinding"}: {}, + } + require.Lenf(GinkgoT(), ip.Status.Plan, len(expectedSteps), "number of expected steps does not match installed: %v", ip.Status.Plan) + + for _, step := range ip.Status.Plan { + key := registry.ResourceKey{ + Name: step.Resource.Name, + Kind: step.Resource.Kind, + } + for expected := range expectedSteps { + if strings.HasPrefix(key.Name, expected.Name) && key.Kind == expected.Kind { + delete(expectedSteps, expected) + } + } + } + require.Lenf(GinkgoT(), expectedSteps, 0, "Actual resource steps do not match expected: %#v", expectedSteps) + }) + + When("unpacking bundle", func() { + var ( + magicCatalog *MagicCatalog + catalogSourceName string + subName string + ) + + BeforeEach(func() { + By("deploying the testing catalog") + provider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.1.0.yaml")) + Expect(err).To(BeNil()) + catalogSourceName = fmt.Sprintf("%s-catsrc", generatedNamespace.GetName()) + magicCatalog = NewMagicCatalog(ctx.Ctx().Client(), generatedNamespace.GetName(), catalogSourceName, provider) + Expect(magicCatalog.DeployCatalog(context.Background())).To(BeNil()) + + By("creating the testing subscription") + subName = fmt.Sprintf("%s-test-package-sub", generatedNamespace.GetName()) + createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subName, catalogSourceName, "test-package", stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) + + By("waiting until the subscription has an IP reference") + subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionHasInstallPlanChecker()) + Expect(err).Should(BeNil()) + + By("waiting for the v0.1.0 CSV to report a succeeded phase") + _, err = fetchCSV(crc, generatedNamespace.GetName(), subscription.Status.CurrentCSV, buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded)) + Expect(err).ShouldNot(HaveOccurred()) + }) + + It("should not report unpacking progress or errors after successfull unpacking", func() { + By("verifying that the subscription is not reporting unpacking progress") + Eventually( + func() (corev1.ConditionStatus, error) { + fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), subName, metav1.GetOptions{}) + if err != nil { + return "", err + } + cond := fetched.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleUnpacking) + return cond.Status, nil + }, + 5*time.Minute, + interval, + ).Should(Equal(corev1.ConditionUnknown)) + + By("verifying that the subscription is not reporting unpacking errors") + Eventually( + func() (corev1.ConditionStatus, error) { + fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), subName, metav1.GetOptions{}) + if err != nil { + return "", err + } + cond := fetched.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleUnpackFailed) + return cond.Status, nil + }, + 5*time.Minute, + interval, + ).Should(Equal(corev1.ConditionUnknown)) + }) + + Context("with bundle which OLM will fail to unpack", func() { + BeforeEach(func() { + By("patching the OperatorGroup to reduce the bundle unpacking timeout") + ogNN := types.NamespacedName{Name: operatorGroup.GetName(), Namespace: generatedNamespace.GetName()} + addBundleUnpackTimeoutOGAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "1s") + + By("updating the catalog with a broken v0.2.0 bundle image") + brokenProvider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.2.0-non-existent-tag.yaml")) + Expect(err).To(BeNil()) + err = magicCatalog.UpdateCatalog(context.Background(), brokenProvider) + Expect(err).To(BeNil()) + }) + + It("should expose a condition indicating failure to unpack", func() { + By("verifying that the subscription is reporting bundle unpack failure condition") + Eventually( + func() (string, error) { + fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), subName, metav1.GetOptions{}) + if err != nil { + return "", err + } + cond := fetched.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleUnpackFailed) + if cond.Status != corev1.ConditionTrue || cond.Reason != "BundleUnpackFailed" { + return "", fmt.Errorf("%s condition not found", operatorsv1alpha1.SubscriptionBundleUnpackFailed) + } + + return cond.Message, nil + }, + 5*time.Minute, + interval, + ).Should(ContainSubstring("bundle unpacking failed. Reason: DeadlineExceeded")) + + By("waiting for the subscription to maintain the example-operator.v0.1.0 status.currentCSV") + Consistently(subscriptionCurrentCSVGetter(crc, generatedNamespace.GetName(), subName)).Should(Equal("example-operator.v0.1.0")) + }) + + It("should be able to recover when catalog gets updated with a fixed version", func() { + By("patching the OperatorGroup to reduce the bundle unpacking timeout") + ogNN := types.NamespacedName{Name: operatorGroup.GetName(), Namespace: generatedNamespace.GetName()} + addBundleUnpackTimeoutOGAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "5m") + + By("updating the catalog with a fixed v0.2.0 bundle image") + brokenProvider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.2.0.yaml")) + Expect(err).To(BeNil()) + err = magicCatalog.UpdateCatalog(context.Background(), brokenProvider) + Expect(err).To(BeNil()) + + By("waiting for the subscription to have v0.2.0 installed") + _, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionHasCurrentCSV("example-operator.v0.2.0")) + Expect(err).Should(BeNil()) + }) + + It("should report deprecation conditions when package, channel, and bundle are referenced in an olm.deprecations object", func() { + By("patching the OperatorGroup to reduce the bundle unpacking timeout") + ogNN := types.NamespacedName{Name: operatorGroup.GetName(), Namespace: generatedNamespace.GetName()} + addBundleUnpackTimeoutOGAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "5m") + + By("updating the catalog with a fixed v0.2.0 bundle image marked deprecated") + provider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.2.0-deprecations.yaml")) + Expect(err).To(BeNil()) + err = magicCatalog.UpdateCatalog(context.Background(), provider) + Expect(err).To(BeNil()) + + By("waiting for the subscription to have v0.2.0 installed with a Bundle Deprecated condition") + sub, err := fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionHasCondition( + operatorsv1alpha1.SubscriptionBundleDeprecated, + corev1.ConditionTrue, + "", + "olm.bundle/example-operator.v0.2.0: bundle \"example-operator.v0.2.0\" has been deprecated. Please switch to a different one.")) + Expect(err).Should(BeNil()) + + By("checking for the deprecated conditions") + By(`Operator is deprecated at all three levels in the catalog`) + packageCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionPackageDeprecated) + Expect(packageCondition.Status).To(Equal(corev1.ConditionTrue)) + channelCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionChannelDeprecated) + Expect(channelCondition.Status).To(Equal(corev1.ConditionTrue)) + bundleCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleDeprecated) + Expect(bundleCondition.Status).To(Equal(corev1.ConditionTrue)) + + By("verifying that a roll-up condition is present containing all deprecation conditions") + By(`Roll-up condition should be present and contain deprecation messages from all three levels`) + rollUpCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionDeprecated) + Expect(rollUpCondition.Status).To(Equal(corev1.ConditionTrue)) + Expect(rollUpCondition.Message).To(ContainSubstring(packageCondition.Message)) + Expect(rollUpCondition.Message).To(ContainSubstring(channelCondition.Message)) + Expect(rollUpCondition.Message).To(ContainSubstring(bundleCondition.Message)) + + By("updating the catalog with a fixed v0.3.0 bundle image no longer marked deprecated") + provider, err = NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.3.0.yaml")) + Expect(err).To(BeNil()) + err = magicCatalog.UpdateCatalog(context.Background(), provider) + Expect(err).To(BeNil()) + + By("waiting for the subscription to have v0.3.0 installed with no deprecation message present") + _, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionDoesNotHaveCondition(operatorsv1alpha1.SubscriptionDeprecated)) + Expect(err).Should(BeNil()) + }) + + It("[FLAKE] should report only package and channel deprecation conditions when bundle is no longer deprecated", func() { + By("patching the OperatorGroup to reduce the bundle unpacking timeout") + ogNN := types.NamespacedName{Name: operatorGroup.GetName(), Namespace: generatedNamespace.GetName()} + addBundleUnpackTimeoutOGAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "5m") + + By("updating the catalog with a fixed v0.2.0 bundle image marked deprecated at all levels") + provider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.2.0-deprecations.yaml")) + Expect(err).To(BeNil()) + err = magicCatalog.UpdateCatalog(context.Background(), provider) + Expect(err).To(BeNil()) + + By("waiting for the subscription to have v0.2.0 installed with a Bundle Deprecated condition") + sub, err := fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionHasCondition( + operatorsv1alpha1.SubscriptionBundleDeprecated, + corev1.ConditionTrue, + "", + "olm.bundle/example-operator.v0.2.0: bundle \"example-operator.v0.2.0\" has been deprecated. Please switch to a different one.")) + Expect(err).Should(BeNil()) + + By("checking for the bundle deprecated condition") + bundleCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleDeprecated) + Expect(bundleCondition.Status).To(Equal(corev1.ConditionTrue)) + bundleDeprecatedMessage := bundleCondition.Message + + By("updating the catalog with a fixed v0.3.0 bundle marked partially deprecated") + provider, err = NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.3.0-deprecations.yaml")) + Expect(err).To(BeNil()) + err = magicCatalog.UpdateCatalog(context.Background(), provider) + Expect(err).To(BeNil()) + + By("waiting for the subscription to switch to v0.3.0") + sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionHasCurrentCSV("example-operator.v0.3.0")) + Expect(err).Should(BeNil()) + + By("waiting for the subscription to have be at latest known") + sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionStateAtLatestChecker()) + Expect(err).Should(BeNil()) + + By("waiting for the subscription to have v0.3.0 installed without a bundle deprecated condition") + sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, + subscriptionHasCondition( + operatorsv1alpha1.SubscriptionInstallPlanPending, + corev1.ConditionUnknown, + "", + "", + ), + ) + Expect(err).Should(BeNil()) + + By("waiting for the subscription to have v0.3.0 installed without a bundle deprecated condition") + sub, err = fetchSubscription(crc, generatedNamespace.GetName(), subName, + subscriptionHasCondition( + operatorsv1alpha1.SubscriptionBundleDeprecated, + corev1.ConditionUnknown, + "", + "", + ), + ) + Expect(err).Should(BeNil()) + + By("checking for the deprecated conditions") + By(`Operator is deprecated at only Package and Channel levels`) + packageCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionPackageDeprecated) + Expect(packageCondition.Status).To(Equal(corev1.ConditionTrue)) + channelCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionChannelDeprecated) + Expect(channelCondition.Status).To(Equal(corev1.ConditionTrue)) + + By("verifying that a roll-up condition is present not containing bundle deprecation condition") + By(`Roll-up condition should be present and contain deprecation messages from Package and Channel levels`) + rollUpCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionDeprecated) + Expect(rollUpCondition.Status).To(Equal(corev1.ConditionTrue)) + Expect(rollUpCondition.Message).To(ContainSubstring(packageCondition.Message)) + Expect(rollUpCondition.Message).To(ContainSubstring(channelCondition.Message)) + Expect(rollUpCondition.Message).ToNot(ContainSubstring(bundleDeprecatedMessage)) + }) + + It("should report deprecated status when catalog is updated to deprecate an installed bundle", func() { + By("patching the OperatorGroup to reduce the bundle unpacking timeout") + ogNN := types.NamespacedName{Name: operatorGroup.GetName(), Namespace: generatedNamespace.GetName()} + addBundleUnpackTimeoutOGAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "5m") + + By("updating the catalog with a fixed v0.2.0 bundle not marked deprecated") + provider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.2.0.yaml")) + Expect(err).To(BeNil()) + err = magicCatalog.UpdateCatalog(context.Background(), provider) + Expect(err).To(BeNil()) + + By("waiting for the subscription to have v0.2.0 installed") + sub, err := fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionHasCurrentCSV("example-operator.v0.2.0")) + Expect(err).Should(BeNil()) + + By("the subscription should not be marked deprecated") + rollupCondition := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionDeprecated) + Expect(rollupCondition.Status).To(Equal(corev1.ConditionUnknown)) + + By("updating the catalog with a fixed v0.2.0 bundle image marked deprecated at all levels") + provider, err = NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.2.0-deprecations.yaml")) + Expect(err).To(BeNil()) + err = magicCatalog.UpdateCatalog(context.Background(), provider) + Expect(err).To(BeNil()) + + By("checking for the bundle deprecated condition") + Eventually(func() (bool, error) { + sub, err := fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionHasCurrentCSV("example-operator.v0.2.0")) + if err != nil { + return false, err + } + cond := sub.Status.GetCondition(operatorsv1alpha1.SubscriptionDeprecated) + if cond.Status != corev1.ConditionTrue { + return false, fmt.Errorf("%s condition not found", operatorsv1alpha1.SubscriptionDeprecated) + } + + return true, nil + }, 2*time.Minute, time.Second*10).Should(BeTrue()) + }) + }) + }) + When("bundle unpack retries are enabled", func() { + It("should retry failing unpack jobs", func() { + if ok, err := inKind(c); ok && err == nil { + Skip("This spec fails when run using KIND cluster. See https://github.com/operator-framework/operator-lifecycle-manager/issues/2420 for more details") + } else if err != nil { + Skip("Could not determine whether running in a kind cluster. Skipping.") + } + By("Ensuring a registry to host bundle images") + local, err := Local(c) + Expect(err).NotTo(HaveOccurred(), "cannot determine if test running locally or on CI: %s", err) + + var registryURL string + var copyImage func(dst, dstTag, src, srcTag string) error + if local { + registryURL, err = createDockerRegistry(c, generatedNamespace.GetName()) + Expect(err).NotTo(HaveOccurred(), "error creating container registry: %s", err) + defer deleteDockerRegistry(c, generatedNamespace.GetName()) + + By(`ensure registry pod is ready before attempting port-forwarding`) + _ = awaitPod(GinkgoT(), c, generatedNamespace.GetName(), registryName, podReady) + + err = registryPortForward(generatedNamespace.GetName()) + Expect(err).NotTo(HaveOccurred(), "port-forwarding local registry: %s", err) + copyImage = func(dst, dstTag, src, srcTag string) error { + if !strings.HasPrefix(src, "docker://") { + src = fmt.Sprintf("docker://%s", src) + } + if !strings.HasPrefix(dst, "docker://") { + dst = fmt.Sprintf("docker://%s", dst) + } + _, err := skopeoLocalCopy(dst, dstTag, src, srcTag) + return err + } + } else { + registryURL = fmt.Sprintf("%s/%s", openshiftregistryFQDN, generatedNamespace.GetName()) + registryAuthSecretName, err := getRegistryAuthSecretName(c, generatedNamespace.GetName()) + Expect(err).NotTo(HaveOccurred(), "error getting openshift registry authentication: %s", err) + copyImage = func(dst, dstTag, src, srcTag string) error { + if !strings.HasPrefix(src, "docker://") { + src = fmt.Sprintf("docker://%s", src) + } + if !strings.HasPrefix(dst, "docker://") { + dst = fmt.Sprintf("docker://%s", dst) + } + skopeoArgs := skopeoCopyCmd(dst, dstTag, src, srcTag, registryAuthSecretName) + err = createSkopeoPod(c, skopeoArgs, generatedNamespace.GetName(), registryAuthSecretName) + if err != nil { + return fmt.Errorf("error creating skopeo pod: %v", err) + } + + By(`wait for skopeo pod to exit successfully`) + awaitPod(GinkgoT(), c, generatedNamespace.GetName(), skopeo, func(pod *corev1.Pod) bool { + ctx.Ctx().Logf("skopeo pod status: %s (waiting for: %s)", pod.Status.Phase, corev1.PodSucceeded) + return pod.Status.Phase == corev1.PodSucceeded + }) + + if err := deleteSkopeoPod(c, generatedNamespace.GetName()); err != nil { + return fmt.Errorf("error deleting skopeo pod: %s", err) + } + return nil + } + } + + By(`The remote image to be copied onto the local registry`) + srcImage := "quay.io/olmtest/example-operator-bundle:" + srcTag := "0.1.0" + + By(`on-cluster image ref`) + bundleImage := registryURL + "/unpack-retry-bundle:" + bundleTag := genName("x") + + unpackRetryCatalog := fmt.Sprintf(` schema: olm.package name: unpack-retry-package defaultChannel: stable @@ -2777,859 +2777,859 @@ properties: version: 1.0.0 `, bundleImage, bundleTag) - By("creating a catalog referencing a non-existent bundle image") - unpackRetryProvider, err := NewRawFileBasedCatalogProvider(unpackRetryCatalog) - Expect(err).ToNot(HaveOccurred()) - catalogSourceName := fmt.Sprintf("%s-catsrc", generatedNamespace.GetName()) - magicCatalog := NewMagicCatalog(ctx.Ctx().Client(), generatedNamespace.GetName(), catalogSourceName, unpackRetryProvider) - Expect(magicCatalog.DeployCatalog(context.Background())).To(BeNil()) - - By("patching the OperatorGroup to reduce the bundle unpacking timeout") - ogNN := types.NamespacedName{Name: operatorGroup.GetName(), Namespace: generatedNamespace.GetName()} - addBundleUnpackTimeoutOGAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "1s") - - By("creating a subscription for the missing bundle") - unpackRetrySubName := fmt.Sprintf("%s-unpack-retry-package-sub", generatedNamespace.GetName()) - createSubscriptionForCatalog(crc, generatedNamespace.GetName(), unpackRetrySubName, catalogSourceName, "unpack-retry-package", stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) - - By("waiting for bundle unpack to fail") - Eventually( - func() error { - fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), unpackRetrySubName, metav1.GetOptions{}) - if err != nil { - return err - } - if cond := fetched.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleUnpackFailed); cond.Status != corev1.ConditionTrue || cond.Reason != "BundleUnpackFailed" { - return fmt.Errorf("%s condition not found", operatorsv1alpha1.SubscriptionBundleUnpackFailed) - } - return nil - }, - 5*time.Minute, - interval, - ).Should(Succeed()) - - By("pushing missing bundle image") - Expect(copyImage(bundleImage, bundleTag, srcImage, srcTag)).To(Succeed()) - - By("patching the OperatorGroup to increase the bundle unpacking timeout") - addBundleUnpackTimeoutOGAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "") // revert to default unpack timeout - - By("patching operator group to enable unpack retries") - setBundleUnpackRetryMinimumIntervalAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "1s") - - By("waiting until the subscription has an IP reference") - subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), unpackRetrySubName, subscriptionHasInstallPlanChecker()) - Expect(err).Should(BeNil()) - - By("waiting for the v0.1.0 CSV to report a succeeded phase") - _, err = fetchCSV(crc, generatedNamespace.GetName(), subscription.Status.CurrentCSV, buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded)) - Expect(err).ShouldNot(HaveOccurred()) - - By("checking if old unpack conditions on subscription are removed") - Eventually(func() error { - fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), unpackRetrySubName, metav1.GetOptions{}) - if err != nil { - return err - } - if cond := fetched.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleUnpacking); cond.Status != corev1.ConditionUnknown { - return fmt.Errorf("subscription condition %s has unexpected value %s, expected %s", operatorsv1alpha1.SubscriptionBundleUnpacking, cond.Status, corev1.ConditionFalse) - } - if cond := fetched.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleUnpackFailed); cond.Status != corev1.ConditionUnknown { - return fmt.Errorf("unexpected condition %s on subscription", operatorsv1alpha1.SubscriptionBundleUnpackFailed) - } - return nil - }).Should(Succeed()) - }) - - It("should not retry successful unpack jobs", func() { - By("deploying the testing catalog") - provider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.1.0.yaml")) - Expect(err).To(BeNil()) - catalogSourceName := fmt.Sprintf("%s-catsrc", generatedNamespace.GetName()) - magicCatalog := NewMagicCatalog(ctx.Ctx().Client(), generatedNamespace.GetName(), catalogSourceName, provider) - Expect(magicCatalog.DeployCatalog(context.Background())).To(BeNil()) - - By("creating the testing subscription") - subName := fmt.Sprintf("%s-test-package-sub", generatedNamespace.GetName()) - createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subName, catalogSourceName, "test-package", stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) - - By("waiting until the subscription has an IP reference") - subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionHasInstallPlanChecker()) - Expect(err).Should(BeNil()) - - By("waiting for the v0.1.0 CSV to report a succeeded phase") - _, err = fetchCSV(crc, generatedNamespace.GetName(), subscription.Status.CurrentCSV, buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded)) - Expect(err).ShouldNot(HaveOccurred()) - - By("patching operator group to enable unpack retries") - ogNN := types.NamespacedName{Name: operatorGroup.GetName(), Namespace: generatedNamespace.GetName()} - setBundleUnpackRetryMinimumIntervalAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "1s") - - By("Ensuring successful bundle unpack jobs are not retried") - Consistently(func() error { - fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), subName, metav1.GetOptions{}) - if err != nil { - return err - } - if cond := fetched.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleUnpacking); cond.Status == corev1.ConditionTrue { - return fmt.Errorf("unexpected condition status for %s on subscription %s", operatorsv1alpha1.SubscriptionBundleUnpacking, subName) - } - if cond := fetched.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleUnpackFailed); cond.Status == corev1.ConditionTrue { - return fmt.Errorf("unexpected condition status for %s on subscription %s", operatorsv1alpha1.SubscriptionBundleUnpackFailed, subName) - } - return nil - }).Should(Succeed()) - }) - }) + By("creating a catalog referencing a non-existent bundle image") + unpackRetryProvider, err := NewRawFileBasedCatalogProvider(unpackRetryCatalog) + Expect(err).ToNot(HaveOccurred()) + catalogSourceName := fmt.Sprintf("%s-catsrc", generatedNamespace.GetName()) + magicCatalog := NewMagicCatalog(ctx.Ctx().Client(), generatedNamespace.GetName(), catalogSourceName, unpackRetryProvider) + Expect(magicCatalog.DeployCatalog(context.Background())).To(BeNil()) + + By("patching the OperatorGroup to reduce the bundle unpacking timeout") + ogNN := types.NamespacedName{Name: operatorGroup.GetName(), Namespace: generatedNamespace.GetName()} + addBundleUnpackTimeoutOGAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "1s") + + By("creating a subscription for the missing bundle") + unpackRetrySubName := fmt.Sprintf("%s-unpack-retry-package-sub", generatedNamespace.GetName()) + createSubscriptionForCatalog(crc, generatedNamespace.GetName(), unpackRetrySubName, catalogSourceName, "unpack-retry-package", stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) + + By("waiting for bundle unpack to fail") + Eventually( + func() error { + fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), unpackRetrySubName, metav1.GetOptions{}) + if err != nil { + return err + } + if cond := fetched.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleUnpackFailed); cond.Status != corev1.ConditionTrue || cond.Reason != "BundleUnpackFailed" { + return fmt.Errorf("%s condition not found", operatorsv1alpha1.SubscriptionBundleUnpackFailed) + } + return nil + }, + 5*time.Minute, + interval, + ).Should(Succeed()) + + By("pushing missing bundle image") + Expect(copyImage(bundleImage, bundleTag, srcImage, srcTag)).To(Succeed()) + + By("patching the OperatorGroup to increase the bundle unpacking timeout") + addBundleUnpackTimeoutOGAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "") // revert to default unpack timeout + + By("patching operator group to enable unpack retries") + setBundleUnpackRetryMinimumIntervalAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "1s") + + By("waiting until the subscription has an IP reference") + subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), unpackRetrySubName, subscriptionHasInstallPlanChecker()) + Expect(err).Should(BeNil()) + + By("waiting for the v0.1.0 CSV to report a succeeded phase") + _, err = fetchCSV(crc, generatedNamespace.GetName(), subscription.Status.CurrentCSV, buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded)) + Expect(err).ShouldNot(HaveOccurred()) + + By("checking if old unpack conditions on subscription are removed") + Eventually(func() error { + fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), unpackRetrySubName, metav1.GetOptions{}) + if err != nil { + return err + } + if cond := fetched.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleUnpacking); cond.Status != corev1.ConditionUnknown { + return fmt.Errorf("subscription condition %s has unexpected value %s, expected %s", operatorsv1alpha1.SubscriptionBundleUnpacking, cond.Status, corev1.ConditionFalse) + } + if cond := fetched.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleUnpackFailed); cond.Status != corev1.ConditionUnknown { + return fmt.Errorf("unexpected condition %s on subscription", operatorsv1alpha1.SubscriptionBundleUnpackFailed) + } + return nil + }).Should(Succeed()) + }) + + It("should not retry successful unpack jobs", func() { + By("deploying the testing catalog") + provider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, subscriptionTestDataBaseDir, "example-operator.v0.1.0.yaml")) + Expect(err).To(BeNil()) + catalogSourceName := fmt.Sprintf("%s-catsrc", generatedNamespace.GetName()) + magicCatalog := NewMagicCatalog(ctx.Ctx().Client(), generatedNamespace.GetName(), catalogSourceName, provider) + Expect(magicCatalog.DeployCatalog(context.Background())).To(BeNil()) + + By("creating the testing subscription") + subName := fmt.Sprintf("%s-test-package-sub", generatedNamespace.GetName()) + createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subName, catalogSourceName, "test-package", stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) + + By("waiting until the subscription has an IP reference") + subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subName, subscriptionHasInstallPlanChecker()) + Expect(err).Should(BeNil()) + + By("waiting for the v0.1.0 CSV to report a succeeded phase") + _, err = fetchCSV(crc, generatedNamespace.GetName(), subscription.Status.CurrentCSV, buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded)) + Expect(err).ShouldNot(HaveOccurred()) + + By("patching operator group to enable unpack retries") + ogNN := types.NamespacedName{Name: operatorGroup.GetName(), Namespace: generatedNamespace.GetName()} + setBundleUnpackRetryMinimumIntervalAnnotation(context.Background(), ctx.Ctx().Client(), ogNN, "1s") + + By("Ensuring successful bundle unpack jobs are not retried") + Consistently(func() error { + fetched, err := crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Get(context.Background(), subName, metav1.GetOptions{}) + if err != nil { + return err + } + if cond := fetched.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleUnpacking); cond.Status == corev1.ConditionTrue { + return fmt.Errorf("unexpected condition status for %s on subscription %s", operatorsv1alpha1.SubscriptionBundleUnpacking, subName) + } + if cond := fetched.Status.GetCondition(operatorsv1alpha1.SubscriptionBundleUnpackFailed); cond.Status == corev1.ConditionTrue { + return fmt.Errorf("unexpected condition status for %s on subscription %s", operatorsv1alpha1.SubscriptionBundleUnpackFailed, subName) + } + return nil + }).Should(Succeed()) + }) + }) }) const ( - catalogSourceName = "mock-ocs" - catalogConfigMapName = "mock-ocs" - testSubscriptionName = "mysubscription" - testPackageName = "myapp" - - stableChannel = "stable" - betaChannel = "beta" - alphaChannel = "alpha" - - outdated = "myapp-outdated" - stable = "myapp-stable" - alpha = "myapp-alpha" - beta = "myapp-beta" + catalogSourceName = "mock-ocs" + catalogConfigMapName = "mock-ocs" + testSubscriptionName = "mysubscription" + testPackageName = "myapp" + + stableChannel = "stable" + betaChannel = "beta" + alphaChannel = "alpha" + + outdated = "myapp-outdated" + stable = "myapp-stable" + alpha = "myapp-alpha" + beta = "myapp-beta" ) var ( - dummyManifest = []registry.PackageManifest{{ - PackageName: testPackageName, - Channels: []registry.PackageChannel{ - {Name: stableChannel, CurrentCSVName: stable}, - {Name: betaChannel, CurrentCSVName: beta}, - {Name: alphaChannel, CurrentCSVName: alpha}, - }, - DefaultChannelName: stableChannel, - }} - csvType = metav1.TypeMeta{ - Kind: operatorsv1alpha1.ClusterServiceVersionKind, - APIVersion: operatorsv1alpha1.GroupVersion, - } - - strategy = operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ - { - Name: genName("dep-"), - Spec: newNginxDeployment(genName("nginx-")), - }, - }, - } - strategyRaw, _ = json.Marshal(strategy) - installStrategy = operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: strategy, - } - outdatedCSV = operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: csvType, - ObjectMeta: metav1.ObjectMeta{ - Name: outdated, - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - Replaces: "", - Version: version.OperatorVersion{Version: semver.MustParse("0.1.0")}, - MinKubeVersion: "0.0.0", - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, - }, - }, - InstallStrategy: installStrategy, - }, - } - stableCSV = operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: csvType, - ObjectMeta: metav1.ObjectMeta{ - Name: stable, - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - Replaces: outdated, - Version: version.OperatorVersion{Version: semver.MustParse("0.2.0")}, - MinKubeVersion: "0.0.0", - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, - }, - }, - InstallStrategy: installStrategy, - }, - } - betaCSV = operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: csvType, - ObjectMeta: metav1.ObjectMeta{ - Name: beta, - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - Replaces: stable, - Version: version.OperatorVersion{Version: semver.MustParse("0.1.1")}, - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, - }, - }, - InstallStrategy: installStrategy, - }, - } - alphaCSV = operatorsv1alpha1.ClusterServiceVersion{ - TypeMeta: csvType, - ObjectMeta: metav1.ObjectMeta{ - Name: alpha, - }, - Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ - Replaces: beta, - Version: version.OperatorVersion{Version: semver.MustParse("0.3.0")}, - InstallModes: []operatorsv1alpha1.InstallMode{ - { - Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, - Supported: true, - }, - { - Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, - Supported: true, - }, - }, - InstallStrategy: installStrategy, - }, - } - csvList = []operatorsv1alpha1.ClusterServiceVersion{outdatedCSV, stableCSV, betaCSV, alphaCSV} - - strategyNew = operatorsv1alpha1.StrategyDetailsDeployment{ - DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ - { - Name: genName("dep-"), - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "nginx"}, - }, - Replicas: &singleInstance, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{"app": "nginx"}, - }, - Spec: corev1.PodSpec{Containers: []corev1.Container{ - { - Name: genName("nginx"), - Image: *dummyImage, - Ports: []corev1.ContainerPort{{ContainerPort: 80}}, - ImagePullPolicy: corev1.PullIfNotPresent, - }, - }}, - }, - }, - }, - }, - } - - dummyCatalogConfigMap = &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: catalogConfigMapName, - }, - Data: map[string]string{}, - } - - dummyCatalogSource = operatorsv1alpha1.CatalogSource{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.CatalogSourceKind, - APIVersion: operatorsv1alpha1.CatalogSourceCRDAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: catalogSourceName, - }, - Spec: operatorsv1alpha1.CatalogSourceSpec{ - SourceType: "internal", - ConfigMap: catalogConfigMapName, - GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ - SecurityContextConfig: operatorsv1alpha1.Restricted, - }, - }, - } + dummyManifest = []registry.PackageManifest{{ + PackageName: testPackageName, + Channels: []registry.PackageChannel{ + {Name: stableChannel, CurrentCSVName: stable}, + {Name: betaChannel, CurrentCSVName: beta}, + {Name: alphaChannel, CurrentCSVName: alpha}, + }, + DefaultChannelName: stableChannel, + }} + csvType = metav1.TypeMeta{ + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + APIVersion: operatorsv1alpha1.GroupVersion, + } + + strategy = operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ + { + Name: genName("dep-"), + Spec: newNginxDeployment(genName("nginx-")), + }, + }, + } + strategyRaw, _ = json.Marshal(strategy) + installStrategy = operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: strategy, + } + outdatedCSV = operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: csvType, + ObjectMeta: metav1.ObjectMeta{ + Name: outdated, + }, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + Replaces: "", + Version: version.OperatorVersion{Version: semver.MustParse("0.1.0")}, + MinKubeVersion: "0.0.0", + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, + }, + InstallStrategy: installStrategy, + }, + } + stableCSV = operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: csvType, + ObjectMeta: metav1.ObjectMeta{ + Name: stable, + }, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + Replaces: outdated, + Version: version.OperatorVersion{Version: semver.MustParse("0.2.0")}, + MinKubeVersion: "0.0.0", + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, + }, + InstallStrategy: installStrategy, + }, + } + betaCSV = operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: csvType, + ObjectMeta: metav1.ObjectMeta{ + Name: beta, + }, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + Replaces: stable, + Version: version.OperatorVersion{Version: semver.MustParse("0.1.1")}, + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, + }, + InstallStrategy: installStrategy, + }, + } + alphaCSV = operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: csvType, + ObjectMeta: metav1.ObjectMeta{ + Name: alpha, + }, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + Replaces: beta, + Version: version.OperatorVersion{Version: semver.MustParse("0.3.0")}, + InstallModes: []operatorsv1alpha1.InstallMode{ + { + Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, + Supported: true, + }, + { + Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, + }, + InstallStrategy: installStrategy, + }, + } + csvList = []operatorsv1alpha1.ClusterServiceVersion{outdatedCSV, stableCSV, betaCSV, alphaCSV} + + strategyNew = operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ + { + Name: genName("dep-"), + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "nginx"}, + }, + Replicas: &singleInstance, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"app": "nginx"}, + }, + Spec: corev1.PodSpec{Containers: []corev1.Container{ + { + Name: genName("nginx"), + Image: *dummyImage, + Ports: []corev1.ContainerPort{{ContainerPort: 80}}, + ImagePullPolicy: corev1.PullIfNotPresent, + }, + }}, + }, + }, + }, + }, + } + + dummyCatalogConfigMap = &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: catalogConfigMapName, + }, + Data: map[string]string{}, + } + + dummyCatalogSource = operatorsv1alpha1.CatalogSource{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.CatalogSourceKind, + APIVersion: operatorsv1alpha1.CatalogSourceCRDAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: catalogSourceName, + }, + Spec: operatorsv1alpha1.CatalogSourceSpec{ + SourceType: "internal", + ConfigMap: catalogConfigMapName, + GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ + SecurityContextConfig: operatorsv1alpha1.Restricted, + }, + }, + } ) func init() { - for i := 0; i < len(csvList); i++ { - csvList[i].Spec.InstallStrategy.StrategySpec = strategyNew - } - - manifestsRaw, err := yaml.Marshal(dummyManifest) - if err != nil { - panic(err) - } - dummyCatalogConfigMap.Data[registry.ConfigMapPackageName] = string(manifestsRaw) - csvsRaw, err := yaml.Marshal(csvList) - if err != nil { - panic(err) - } - dummyCatalogConfigMap.Data[registry.ConfigMapCSVName] = string(csvsRaw) - dummyCatalogConfigMap.Data[registry.ConfigMapCRDName] = "" + for i := 0; i < len(csvList); i++ { + csvList[i].Spec.InstallStrategy.StrategySpec = strategyNew + } + + manifestsRaw, err := yaml.Marshal(dummyManifest) + if err != nil { + panic(err) + } + dummyCatalogConfigMap.Data[registry.ConfigMapPackageName] = string(manifestsRaw) + csvsRaw, err := yaml.Marshal(csvList) + if err != nil { + panic(err) + } + dummyCatalogConfigMap.Data[registry.ConfigMapCSVName] = string(csvsRaw) + dummyCatalogConfigMap.Data[registry.ConfigMapCRDName] = "" } func initCatalog(t GinkgoTInterface, namespace string, c operatorclient.ClientInterface, crc versioned.Interface) error { - dummyCatalogConfigMap.SetNamespace(namespace) - if _, err := c.KubernetesInterface().CoreV1().ConfigMaps(namespace).Create(context.Background(), dummyCatalogConfigMap, metav1.CreateOptions{}); err != nil { - if apierrors.IsAlreadyExists(err) { - return fmt.Errorf("E2E bug detected: %v", err) - } - return err - } - t.Logf("created configmap %s/%s", dummyCatalogConfigMap.Namespace, dummyCatalogConfigMap.Name) - - dummyCatalogSource.SetNamespace(namespace) - if _, err := crc.OperatorsV1alpha1().CatalogSources(namespace).Create(context.Background(), &dummyCatalogSource, metav1.CreateOptions{}); err != nil { - if apierrors.IsAlreadyExists(err) { - return fmt.Errorf("E2E bug detected: %v", err) - } - return err - } - t.Logf("created catalog source %s/%s", dummyCatalogSource.Namespace, dummyCatalogSource.Name) - - fetched, err := fetchCatalogSourceOnStatus(crc, dummyCatalogSource.GetName(), dummyCatalogSource.GetNamespace(), catalogSourceRegistryPodSynced()) - require.NoError(t, err) - require.NotNil(t, fetched) - - return nil + dummyCatalogConfigMap.SetNamespace(namespace) + if _, err := c.KubernetesInterface().CoreV1().ConfigMaps(namespace).Create(context.Background(), dummyCatalogConfigMap, metav1.CreateOptions{}); err != nil { + if apierrors.IsAlreadyExists(err) { + return fmt.Errorf("E2E bug detected: %v", err) + } + return err + } + t.Logf("created configmap %s/%s", dummyCatalogConfigMap.Namespace, dummyCatalogConfigMap.Name) + + dummyCatalogSource.SetNamespace(namespace) + if _, err := crc.OperatorsV1alpha1().CatalogSources(namespace).Create(context.Background(), &dummyCatalogSource, metav1.CreateOptions{}); err != nil { + if apierrors.IsAlreadyExists(err) { + return fmt.Errorf("E2E bug detected: %v", err) + } + return err + } + t.Logf("created catalog source %s/%s", dummyCatalogSource.Namespace, dummyCatalogSource.Name) + + fetched, err := fetchCatalogSourceOnStatus(crc, dummyCatalogSource.GetName(), dummyCatalogSource.GetNamespace(), catalogSourceRegistryPodSynced()) + require.NoError(t, err) + require.NotNil(t, fetched) + + return nil } type subscriptionStateChecker func(subscription *operatorsv1alpha1.Subscription) bool func subscriptionStateUpgradeAvailableChecker() func(subscription *operatorsv1alpha1.Subscription) bool { - var lastState operatorsv1alpha1.SubscriptionState - lastTime := time.Now() - return func(subscription *operatorsv1alpha1.Subscription) bool { - if subscription.Status.State != lastState { - 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) - lastState = subscription.Status.State - lastTime = time.Now() - } - return subscription.Status.State == operatorsv1alpha1.SubscriptionStateUpgradeAvailable - } + var lastState operatorsv1alpha1.SubscriptionState + lastTime := time.Now() + return func(subscription *operatorsv1alpha1.Subscription) bool { + if subscription.Status.State != lastState { + 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) + lastState = subscription.Status.State + lastTime = time.Now() + } + return subscription.Status.State == operatorsv1alpha1.SubscriptionStateUpgradeAvailable + } } func subscriptionStateUpgradePendingChecker() func(subscription *operatorsv1alpha1.Subscription) bool { - var lastState operatorsv1alpha1.SubscriptionState - lastTime := time.Now() - return func(subscription *operatorsv1alpha1.Subscription) bool { - if subscription.Status.State != lastState { - 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) - lastState = subscription.Status.State - lastTime = time.Now() - } - return subscription.Status.State == operatorsv1alpha1.SubscriptionStateUpgradePending - } + var lastState operatorsv1alpha1.SubscriptionState + lastTime := time.Now() + return func(subscription *operatorsv1alpha1.Subscription) bool { + if subscription.Status.State != lastState { + 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) + lastState = subscription.Status.State + lastTime = time.Now() + } + return subscription.Status.State == operatorsv1alpha1.SubscriptionStateUpgradePending + } } func subscriptionStateAtLatestChecker() func(subscription *operatorsv1alpha1.Subscription) bool { - var lastState operatorsv1alpha1.SubscriptionState - lastTime := time.Now() - return func(subscription *operatorsv1alpha1.Subscription) bool { - if subscription.Status.State != lastState { - 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) - lastState = subscription.Status.State - lastTime = time.Now() - } - return subscription.Status.State == operatorsv1alpha1.SubscriptionStateAtLatest - } + var lastState operatorsv1alpha1.SubscriptionState + lastTime := time.Now() + return func(subscription *operatorsv1alpha1.Subscription) bool { + if subscription.Status.State != lastState { + 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) + lastState = subscription.Status.State + lastTime = time.Now() + } + return subscription.Status.State == operatorsv1alpha1.SubscriptionStateAtLatest + } } func subscriptionHasInstallPlanChecker() func(subscription *operatorsv1alpha1.Subscription) bool { - var lastState operatorsv1alpha1.SubscriptionState - lastTime := time.Now() - return func(subscription *operatorsv1alpha1.Subscription) bool { - if subscription.Status.State != lastState { - 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) - lastState = subscription.Status.State - lastTime = time.Now() - } - return subscription.Status.InstallPlanRef != nil - } + var lastState operatorsv1alpha1.SubscriptionState + lastTime := time.Now() + return func(subscription *operatorsv1alpha1.Subscription) bool { + if subscription.Status.State != lastState { + 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) + lastState = subscription.Status.State + lastTime = time.Now() + } + return subscription.Status.InstallPlanRef != nil + } } func subscriptionHasInstallPlanDifferentChecker(currentInstallPlanName string) subscriptionStateChecker { - checker := subscriptionHasInstallPlanChecker() - var lastState operatorsv1alpha1.SubscriptionState - lastTime := time.Now() - return func(subscription *operatorsv1alpha1.Subscription) bool { - if subscription.Status.State != lastState { - 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) - lastState = subscription.Status.State - lastTime = time.Now() - } - return checker(subscription) && subscription.Status.InstallPlanRef.Name != currentInstallPlanName - } + checker := subscriptionHasInstallPlanChecker() + var lastState operatorsv1alpha1.SubscriptionState + lastTime := time.Now() + return func(subscription *operatorsv1alpha1.Subscription) bool { + if subscription.Status.State != lastState { + 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) + lastState = subscription.Status.State + lastTime = time.Now() + } + return checker(subscription) && subscription.Status.InstallPlanRef.Name != currentInstallPlanName + } } func subscriptionHasCurrentCSV(currentCSV string) subscriptionStateChecker { - return func(subscription *operatorsv1alpha1.Subscription) bool { - return subscription.Status.CurrentCSV == currentCSV - } + return func(subscription *operatorsv1alpha1.Subscription) bool { + return subscription.Status.CurrentCSV == currentCSV + } } func subscriptionHasCondition(condType operatorsv1alpha1.SubscriptionConditionType, status corev1.ConditionStatus, reason, message string) subscriptionStateChecker { - var lastCond operatorsv1alpha1.SubscriptionCondition - lastTime := time.Now() - // if status/reason/message meet expectations, then subscription state is considered met/true - // IFF this is the result of a recent change of status/reason/message - // else, cache the current status/reason/message for next loop/comparison - return func(subscription *operatorsv1alpha1.Subscription) bool { - cond := subscription.Status.GetCondition(condType) - if cond.Status == status && cond.Reason == reason && cond.Message == message { - if lastCond.Status != cond.Status && lastCond.Reason != cond.Reason && lastCond.Message == cond.Message { - GinkgoT().Logf("waited %s subscription condition met %v\n", time.Since(lastTime), cond) - lastTime = time.Now() - lastCond = cond - } - return true - } - - if lastCond.Status != cond.Status && lastCond.Reason != cond.Reason && lastCond.Message == cond.Message { - GinkgoT().Logf("waited %s subscription condition not met: %v\n", time.Since(lastTime), cond) - lastTime = time.Now() - lastCond = cond - } - return false - } + var lastCond operatorsv1alpha1.SubscriptionCondition + lastTime := time.Now() + // if status/reason/message meet expectations, then subscription state is considered met/true + // IFF this is the result of a recent change of status/reason/message + // else, cache the current status/reason/message for next loop/comparison + return func(subscription *operatorsv1alpha1.Subscription) bool { + cond := subscription.Status.GetCondition(condType) + if cond.Status == status && cond.Reason == reason && cond.Message == message { + if lastCond.Status != cond.Status && lastCond.Reason != cond.Reason && lastCond.Message == cond.Message { + GinkgoT().Logf("waited %s subscription condition met %v\n", time.Since(lastTime), cond) + lastTime = time.Now() + lastCond = cond + } + return true + } + + if lastCond.Status != cond.Status && lastCond.Reason != cond.Reason && lastCond.Message == cond.Message { + GinkgoT().Logf("waited %s subscription condition not met: %v\n", time.Since(lastTime), cond) + lastTime = time.Now() + lastCond = cond + } + return false + } } func subscriptionDoesNotHaveCondition(condType operatorsv1alpha1.SubscriptionConditionType) subscriptionStateChecker { - var lastStatus corev1.ConditionStatus - lastTime := time.Now() - // if status meets expectations, then subscription state is considered met/true - // IFF this is the result of a recent change of status - // else, cache the current status for next loop/comparison - return func(subscription *operatorsv1alpha1.Subscription) bool { - cond := subscription.Status.GetCondition(condType) - if cond.Status == corev1.ConditionUnknown { - if cond.Status != lastStatus { - GinkgoT().Logf("waited %s subscription condition not found\n", time.Since(lastTime)) - lastStatus = cond.Status - lastTime = time.Now() - } - return true - } - - if cond.Status != lastStatus { - GinkgoT().Logf("waited %s subscription condition found: %v\n", time.Since(lastTime), cond) - lastStatus = cond.Status - lastTime = time.Now() - } - return false - } + var lastStatus corev1.ConditionStatus + lastTime := time.Now() + // if status meets expectations, then subscription state is considered met/true + // IFF this is the result of a recent change of status + // else, cache the current status for next loop/comparison + return func(subscription *operatorsv1alpha1.Subscription) bool { + cond := subscription.Status.GetCondition(condType) + if cond.Status == corev1.ConditionUnknown { + if cond.Status != lastStatus { + GinkgoT().Logf("waited %s subscription condition not found\n", time.Since(lastTime)) + lastStatus = cond.Status + lastTime = time.Now() + } + return true + } + + if cond.Status != lastStatus { + GinkgoT().Logf("waited %s subscription condition found: %v\n", time.Since(lastTime), cond) + lastStatus = cond.Status + lastTime = time.Now() + } + return false + } } func fetchSubscription(crc versioned.Interface, namespace, name string, checker subscriptionStateChecker) (*operatorsv1alpha1.Subscription, error) { - var fetchedSubscription *operatorsv1alpha1.Subscription - - log := func(s string) { - ctx.Ctx().Logf("%s: %s", time.Now().Format("15:04:05.9999"), s) - } - - var lastState operatorsv1alpha1.SubscriptionState - var lastCSV string - var lastInstallPlanRef *corev1.ObjectReference - - err := wait.Poll(pollInterval, pollDuration, func() (bool, error) { - var err error - fetchedSubscription, err = crc.OperatorsV1alpha1().Subscriptions(namespace).Get(context.Background(), name, metav1.GetOptions{}) - if err != nil || fetchedSubscription == nil { - log(fmt.Sprintf("error getting subscription %s/%s: %v", namespace, name, err)) - return false, nil - } - thisState, thisCSV, thisInstallPlanRef := fetchedSubscription.Status.State, fetchedSubscription.Status.CurrentCSV, fetchedSubscription.Status.InstallPlanRef - if thisState != lastState || thisCSV != lastCSV || !equality.Semantic.DeepEqual(thisInstallPlanRef, lastInstallPlanRef) { - lastState, lastCSV, lastInstallPlanRef = thisState, thisCSV, thisInstallPlanRef - log(fmt.Sprintf("subscription %s/%s state: %s (csv %s): installPlanRef: %#v", namespace, name, thisState, thisCSV, thisInstallPlanRef)) - log(fmt.Sprintf("subscription %s/%s state: %s (csv %s): status: %#v", namespace, name, thisState, thisCSV, fetchedSubscription.Status)) - } - return checker(fetchedSubscription), nil - }) - if err != nil { - log(fmt.Sprintf("subscription %s/%s never got correct status: %#v", namespace, name, fetchedSubscription.Status)) - log(fmt.Sprintf("subscription %s/%s spec: %#v", namespace, name, fetchedSubscription.Spec)) - return nil, err - } - return fetchedSubscription, nil + var fetchedSubscription *operatorsv1alpha1.Subscription + + log := func(s string) { + ctx.Ctx().Logf("%s: %s", time.Now().Format("15:04:05.9999"), s) + } + + var lastState operatorsv1alpha1.SubscriptionState + var lastCSV string + var lastInstallPlanRef *corev1.ObjectReference + + err := wait.PollUntilContextTimeout(context.Background(), pollInterval, pollDuration, true, func(ctx context.Context) (bool, error) { + var err error + fetchedSubscription, err = crc.OperatorsV1alpha1().Subscriptions(namespace).Get(ctx, name, metav1.GetOptions{}) + if err != nil || fetchedSubscription == nil { + log(fmt.Sprintf("error getting subscription %s/%s: %v", namespace, name, err)) + return false, nil + } + thisState, thisCSV, thisInstallPlanRef := fetchedSubscription.Status.State, fetchedSubscription.Status.CurrentCSV, fetchedSubscription.Status.InstallPlanRef + if thisState != lastState || thisCSV != lastCSV || !equality.Semantic.DeepEqual(thisInstallPlanRef, lastInstallPlanRef) { + lastState, lastCSV, lastInstallPlanRef = thisState, thisCSV, thisInstallPlanRef + log(fmt.Sprintf("subscription %s/%s state: %s (csv %s): installPlanRef: %#v", namespace, name, thisState, thisCSV, thisInstallPlanRef)) + log(fmt.Sprintf("subscription %s/%s state: %s (csv %s): status: %#v", namespace, name, thisState, thisCSV, fetchedSubscription.Status)) + } + return checker(fetchedSubscription), nil + }) + if err != nil { + log(fmt.Sprintf("subscription %s/%s never got correct status: %#v", namespace, name, fetchedSubscription.Status)) + log(fmt.Sprintf("subscription %s/%s spec: %#v", namespace, name, fetchedSubscription.Spec)) + return nil, err + } + return fetchedSubscription, nil } func buildSubscriptionCleanupFunc(crc versioned.Interface, subscription *operatorsv1alpha1.Subscription) cleanupFunc { - return func() { - if env := os.Getenv("SKIP_CLEANUP"); env != "" { - fmt.Printf("Skipping cleanup of install plan for subscription %s/%s...\n", subscription.GetNamespace(), subscription.GetName()) - return - } - - if installPlanRef := subscription.Status.InstallPlanRef; installPlanRef != nil { - installPlan, err := crc.OperatorsV1alpha1().InstallPlans(subscription.GetNamespace()).Get(context.Background(), installPlanRef.Name, metav1.GetOptions{}) - if err == nil { - buildInstallPlanCleanupFunc(crc, subscription.GetNamespace(), installPlan)() - } else { - ctx.Ctx().Logf("Could not get installplan %s while building subscription %s's cleanup function", installPlan.GetName(), subscription.GetName()) - } - } - - err := crc.OperatorsV1alpha1().Subscriptions(subscription.GetNamespace()).Delete(context.Background(), subscription.GetName(), metav1.DeleteOptions{}) - Expect(err).NotTo(HaveOccurred()) - } + return func() { + if env := os.Getenv("SKIP_CLEANUP"); env != "" { + fmt.Printf("Skipping cleanup of install plan for subscription %s/%s...\n", subscription.GetNamespace(), subscription.GetName()) + return + } + + if installPlanRef := subscription.Status.InstallPlanRef; installPlanRef != nil { + installPlan, err := crc.OperatorsV1alpha1().InstallPlans(subscription.GetNamespace()).Get(context.Background(), installPlanRef.Name, metav1.GetOptions{}) + if err == nil { + buildInstallPlanCleanupFunc(crc, subscription.GetNamespace(), installPlan)() + } else { + ctx.Ctx().Logf("Could not get installplan %s while building subscription %s's cleanup function", installPlan.GetName(), subscription.GetName()) + } + } + + err := crc.OperatorsV1alpha1().Subscriptions(subscription.GetNamespace()).Delete(context.Background(), subscription.GetName(), metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + } } func createSubscription(t GinkgoTInterface, crc versioned.Interface, namespace, name, packageName, channel string, approval operatorsv1alpha1.Approval) (cleanupFunc, *operatorsv1alpha1.Subscription) { - subscription := &operatorsv1alpha1.Subscription{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.SubscriptionKind, - APIVersion: operatorsv1alpha1.SubscriptionCRDAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - }, - Spec: &operatorsv1alpha1.SubscriptionSpec{ - CatalogSource: catalogSourceName, - CatalogSourceNamespace: namespace, - Package: packageName, - Channel: channel, - InstallPlanApproval: approval, - }, - } - - subscription, err := crc.OperatorsV1alpha1().Subscriptions(namespace).Create(context.Background(), subscription, metav1.CreateOptions{}) - Expect(err).ToNot(HaveOccurred()) - t.Logf("created subscription %s/%s", subscription.Namespace, subscription.Name) - return buildSubscriptionCleanupFunc(crc, subscription), subscription + subscription := &operatorsv1alpha1.Subscription{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.SubscriptionKind, + APIVersion: operatorsv1alpha1.SubscriptionCRDAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + }, + Spec: &operatorsv1alpha1.SubscriptionSpec{ + CatalogSource: catalogSourceName, + CatalogSourceNamespace: namespace, + Package: packageName, + Channel: channel, + InstallPlanApproval: approval, + }, + } + + subscription, err := crc.OperatorsV1alpha1().Subscriptions(namespace).Create(context.Background(), subscription, metav1.CreateOptions{}) + Expect(err).ToNot(HaveOccurred()) + t.Logf("created subscription %s/%s", subscription.Namespace, subscription.Name) + return buildSubscriptionCleanupFunc(crc, subscription), subscription } func createSubscriptionForCatalog(crc versioned.Interface, namespace, name, catalog, packageName, channel, startingCSV string, approval operatorsv1alpha1.Approval) cleanupFunc { - subscription := &operatorsv1alpha1.Subscription{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.SubscriptionKind, - APIVersion: operatorsv1alpha1.SubscriptionCRDAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - }, - Spec: &operatorsv1alpha1.SubscriptionSpec{ - CatalogSource: catalog, - CatalogSourceNamespace: namespace, - Package: packageName, - Channel: channel, - StartingCSV: startingCSV, - InstallPlanApproval: approval, - }, - } - - subscription, err := crc.OperatorsV1alpha1().Subscriptions(namespace).Create(context.Background(), subscription, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - return buildSubscriptionCleanupFunc(crc, subscription) + subscription := &operatorsv1alpha1.Subscription{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.SubscriptionKind, + APIVersion: operatorsv1alpha1.SubscriptionCRDAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + }, + Spec: &operatorsv1alpha1.SubscriptionSpec{ + CatalogSource: catalog, + CatalogSourceNamespace: namespace, + Package: packageName, + Channel: channel, + StartingCSV: startingCSV, + InstallPlanApproval: approval, + }, + } + + subscription, err := crc.OperatorsV1alpha1().Subscriptions(namespace).Create(context.Background(), subscription, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + return buildSubscriptionCleanupFunc(crc, subscription) } func createSubscriptionForCatalogWithSpec(t GinkgoTInterface, crc versioned.Interface, namespace, name string, spec *operatorsv1alpha1.SubscriptionSpec) cleanupFunc { - subscription := &operatorsv1alpha1.Subscription{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.SubscriptionKind, - APIVersion: operatorsv1alpha1.SubscriptionCRDAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - }, - Spec: spec, - } - - subscription, err := crc.OperatorsV1alpha1().Subscriptions(namespace).Create(context.Background(), subscription, metav1.CreateOptions{}) - require.NoError(t, err) - return buildSubscriptionCleanupFunc(crc, subscription) + subscription := &operatorsv1alpha1.Subscription{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.SubscriptionKind, + APIVersion: operatorsv1alpha1.SubscriptionCRDAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + }, + Spec: spec, + } + + subscription, err := crc.OperatorsV1alpha1().Subscriptions(namespace).Create(context.Background(), subscription, metav1.CreateOptions{}) + require.NoError(t, err) + return buildSubscriptionCleanupFunc(crc, subscription) } func waitForSubscriptionToDelete(namespace, name string, c versioned.Interface) error { - var lastState operatorsv1alpha1.SubscriptionState - var lastReason operatorsv1alpha1.ConditionReason - lastTime := time.Now() - - ctx.Ctx().Logf("waiting for subscription %s/%s to delete", namespace, name) - err := wait.Poll(pollInterval, pollDuration, func() (bool, error) { - sub, err := c.OperatorsV1alpha1().Subscriptions(namespace).Get(context.TODO(), name, metav1.GetOptions{}) - if apierrors.IsNotFound(err) { - ctx.Ctx().Logf("subscription %s/%s deleted", namespace, name) - return true, nil - } - if err != nil { - ctx.Ctx().Logf("error getting subscription %s/%s: %v", namespace, name, err) - } - if sub != nil { - state, reason := sub.Status.State, sub.Status.Reason - if state != lastState || reason != lastReason { - ctx.Ctx().Logf("waited %s for subscription %s/%s status: %s (%s)", time.Since(lastTime), namespace, name, state, reason) - lastState, lastReason = state, reason - lastTime = time.Now() - } - } - return false, nil - }) - - return err + var lastState operatorsv1alpha1.SubscriptionState + var lastReason operatorsv1alpha1.ConditionReason + lastTime := time.Now() + + ctx.Ctx().Logf("waiting for subscription %s/%s to delete", namespace, name) + err := wait.Poll(pollInterval, pollDuration, func() (bool, error) { + sub, err := c.OperatorsV1alpha1().Subscriptions(namespace).Get(context.TODO(), name, metav1.GetOptions{}) + if apierrors.IsNotFound(err) { + ctx.Ctx().Logf("subscription %s/%s deleted", namespace, name) + return true, nil + } + if err != nil { + ctx.Ctx().Logf("error getting subscription %s/%s: %v", namespace, name, err) + } + if sub != nil { + state, reason := sub.Status.State, sub.Status.Reason + if state != lastState || reason != lastReason { + ctx.Ctx().Logf("waited %s for subscription %s/%s status: %s (%s)", time.Since(lastTime), namespace, name, state, reason) + lastState, lastReason = state, reason + lastTime = time.Now() + } + } + return false, nil + }) + + return err } func checkDeploymentHasPodConfigNodeSelector(t GinkgoTInterface, client operatorclient.ClientInterface, csv *operatorsv1alpha1.ClusterServiceVersion, nodeSelector map[string]string) error { - resolver := install.StrategyResolver{} + resolver := install.StrategyResolver{} - strategy, err := resolver.UnmarshalStrategy(csv.Spec.InstallStrategy) - if err != nil { - return err - } + strategy, err := resolver.UnmarshalStrategy(csv.Spec.InstallStrategy) + if err != nil { + return err + } - strategyDetailsDeployment, ok := strategy.(*operatorsv1alpha1.StrategyDetailsDeployment) - require.Truef(t, ok, "could not cast install strategy as type %T", strategyDetailsDeployment) + strategyDetailsDeployment, ok := strategy.(*operatorsv1alpha1.StrategyDetailsDeployment) + require.Truef(t, ok, "could not cast install strategy as type %T", strategyDetailsDeployment) - for _, deploymentSpec := range strategyDetailsDeployment.DeploymentSpecs { - deployment, err := client.KubernetesInterface().AppsV1().Deployments(csv.GetNamespace()).Get(context.Background(), deploymentSpec.Name, metav1.GetOptions{}) - if err != nil { - return err - } + for _, deploymentSpec := range strategyDetailsDeployment.DeploymentSpecs { + deployment, err := client.KubernetesInterface().AppsV1().Deployments(csv.GetNamespace()).Get(context.Background(), deploymentSpec.Name, metav1.GetOptions{}) + if err != nil { + return err + } - isEqual := reflect.DeepEqual(nodeSelector, deployment.Spec.Template.Spec.NodeSelector) - if !isEqual { - err = fmt.Errorf("actual nodeSelector=%v does not match expected nodeSelector=%v", deploymentSpec.Spec.Template.Spec.NodeSelector, nodeSelector) - } - } - return nil + isEqual := reflect.DeepEqual(nodeSelector, deployment.Spec.Template.Spec.NodeSelector) + if !isEqual { + err = fmt.Errorf("actual nodeSelector=%v does not match expected nodeSelector=%v", deploymentSpec.Spec.Template.Spec.NodeSelector, nodeSelector) + } + } + return nil } func checkDeploymentWithPodConfiguration(client operatorclient.ClientInterface, csv *operatorsv1alpha1.ClusterServiceVersion, envVar []corev1.EnvVar, volumes []corev1.Volume, volumeMounts []corev1.VolumeMount, tolerations []corev1.Toleration, resources *corev1.ResourceRequirements) error { - resolver := install.StrategyResolver{} - - strategy, err := resolver.UnmarshalStrategy(csv.Spec.InstallStrategy) - Expect(err).NotTo(HaveOccurred()) - - strategyDetailsDeployment, ok := strategy.(*operatorsv1alpha1.StrategyDetailsDeployment) - Expect(ok).To(BeTrue(), "could not cast install strategy as type %T", strategyDetailsDeployment) - - findEnvVar := func(envVar []corev1.EnvVar, name string) (foundEnvVar *corev1.EnvVar, found bool) { - for i := range envVar { - if name == envVar[i].Name { - found = true - foundEnvVar = &envVar[i] - - break - } - } - - return - } - - findVolumeMount := func(volumeMounts []corev1.VolumeMount, name string) (foundVolumeMount *corev1.VolumeMount, found bool) { - for i := range volumeMounts { - if name == volumeMounts[i].Name { - found = true - foundVolumeMount = &volumeMounts[i] - - break - } - } - - return - } - - findVolume := func(volumes []corev1.Volume, name string) (foundVolume *corev1.Volume, found bool) { - for i := range volumes { - if name == volumes[i].Name { - found = true - foundVolume = &volumes[i] - - break - } - } - - return - } - - findTolerations := func(tolerations []corev1.Toleration, toleration corev1.Toleration) (foundToleration *corev1.Toleration, found bool) { - for i := range tolerations { - if reflect.DeepEqual(toleration, tolerations[i]) { - found = true - foundToleration = &toleration - - break - } - } - - return - } - - findResources := func(existingResource *corev1.ResourceRequirements, podResource *corev1.ResourceRequirements) (foundResource *corev1.ResourceRequirements, found bool) { - if reflect.DeepEqual(existingResource, podResource) { - found = true - foundResource = podResource - } - - return - } - - check := func(container *corev1.Container) error { - for _, e := range envVar { - existing, found := findEnvVar(container.Env, e.Name) - if !found || existing == nil { - return fmt.Errorf("env variable name=%s not injected", e.Name) - } - Expect(e.Value).Should(Equal(existing.Value), "env variable value does not match %s=%s", e.Name, e.Value) - } - - for _, v := range volumeMounts { - existing, found := findVolumeMount(container.VolumeMounts, v.Name) - if !found || existing == nil { - return fmt.Errorf("VolumeMount name=%s not injected", v.Name) - } - Expect(v.MountPath).Should(Equal(existing.MountPath), "VolumeMount MountPath does not match %s=%s", v.Name, v.MountPath) - } - - existing, found := findResources(&container.Resources, resources) - if !found || existing == nil { - return fmt.Errorf("Resources not injected. Resource=%v", resources) - } - Expect(existing).Should(Equal(resources), "Resource=%v does not match expected Resource=%v", existing, resources) - return nil - } - - for _, deploymentSpec := range strategyDetailsDeployment.DeploymentSpecs { - deployment, err := client.KubernetesInterface().AppsV1().Deployments(csv.GetNamespace()).Get(context.Background(), deploymentSpec.Name, metav1.GetOptions{}) - Expect(err).NotTo(HaveOccurred()) - for _, v := range volumes { - existing, found := findVolume(deployment.Spec.Template.Spec.Volumes, v.Name) - if !found || existing == nil { - return fmt.Errorf("Volume name=%s not injected", v.Name) - } - 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) - } - - for _, toleration := range tolerations { - existing, found := findTolerations(deployment.Spec.Template.Spec.Tolerations, toleration) - if !found || existing == nil { - return fmt.Errorf("Toleration not injected. Toleration=%v", toleration) - } - Expect(*existing).Should(Equal(toleration), "Toleration=%v does not match expected Toleration=%v", existing, toleration) - } - - for i := range deployment.Spec.Template.Spec.Containers { - err = check(&deployment.Spec.Template.Spec.Containers[i]) - if err != nil { - return err - } - } - } - return nil + resolver := install.StrategyResolver{} + + strategy, err := resolver.UnmarshalStrategy(csv.Spec.InstallStrategy) + Expect(err).NotTo(HaveOccurred()) + + strategyDetailsDeployment, ok := strategy.(*operatorsv1alpha1.StrategyDetailsDeployment) + Expect(ok).To(BeTrue(), "could not cast install strategy as type %T", strategyDetailsDeployment) + + findEnvVar := func(envVar []corev1.EnvVar, name string) (foundEnvVar *corev1.EnvVar, found bool) { + for i := range envVar { + if name == envVar[i].Name { + found = true + foundEnvVar = &envVar[i] + + break + } + } + + return + } + + findVolumeMount := func(volumeMounts []corev1.VolumeMount, name string) (foundVolumeMount *corev1.VolumeMount, found bool) { + for i := range volumeMounts { + if name == volumeMounts[i].Name { + found = true + foundVolumeMount = &volumeMounts[i] + + break + } + } + + return + } + + findVolume := func(volumes []corev1.Volume, name string) (foundVolume *corev1.Volume, found bool) { + for i := range volumes { + if name == volumes[i].Name { + found = true + foundVolume = &volumes[i] + + break + } + } + + return + } + + findTolerations := func(tolerations []corev1.Toleration, toleration corev1.Toleration) (foundToleration *corev1.Toleration, found bool) { + for i := range tolerations { + if reflect.DeepEqual(toleration, tolerations[i]) { + found = true + foundToleration = &toleration + + break + } + } + + return + } + + findResources := func(existingResource *corev1.ResourceRequirements, podResource *corev1.ResourceRequirements) (foundResource *corev1.ResourceRequirements, found bool) { + if reflect.DeepEqual(existingResource, podResource) { + found = true + foundResource = podResource + } + + return + } + + check := func(container *corev1.Container) error { + for _, e := range envVar { + existing, found := findEnvVar(container.Env, e.Name) + if !found || existing == nil { + return fmt.Errorf("env variable name=%s not injected", e.Name) + } + Expect(e.Value).Should(Equal(existing.Value), "env variable value does not match %s=%s", e.Name, e.Value) + } + + for _, v := range volumeMounts { + existing, found := findVolumeMount(container.VolumeMounts, v.Name) + if !found || existing == nil { + return fmt.Errorf("VolumeMount name=%s not injected", v.Name) + } + Expect(v.MountPath).Should(Equal(existing.MountPath), "VolumeMount MountPath does not match %s=%s", v.Name, v.MountPath) + } + + existing, found := findResources(&container.Resources, resources) + if !found || existing == nil { + return fmt.Errorf("Resources not injected. Resource=%v", resources) + } + Expect(existing).Should(Equal(resources), "Resource=%v does not match expected Resource=%v", existing, resources) + return nil + } + + for _, deploymentSpec := range strategyDetailsDeployment.DeploymentSpecs { + deployment, err := client.KubernetesInterface().AppsV1().Deployments(csv.GetNamespace()).Get(context.Background(), deploymentSpec.Name, metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) + for _, v := range volumes { + existing, found := findVolume(deployment.Spec.Template.Spec.Volumes, v.Name) + if !found || existing == nil { + return fmt.Errorf("Volume name=%s not injected", v.Name) + } + 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) + } + + for _, toleration := range tolerations { + existing, found := findTolerations(deployment.Spec.Template.Spec.Tolerations, toleration) + if !found || existing == nil { + return fmt.Errorf("Toleration not injected. Toleration=%v", toleration) + } + Expect(*existing).Should(Equal(toleration), "Toleration=%v does not match expected Toleration=%v", existing, toleration) + } + + for i := range deployment.Spec.Template.Spec.Containers { + err = check(&deployment.Spec.Template.Spec.Containers[i]) + if err != nil { + return err + } + } + } + return nil } func updateInternalCatalog(t GinkgoTInterface, c operatorclient.ClientInterface, crc versioned.Interface, catalogSourceName, namespace string, crds []apiextensionsv1.CustomResourceDefinition, csvs []operatorsv1alpha1.ClusterServiceVersion, packages []registry.PackageManifest) { - fetchedInitialCatalog, err := fetchCatalogSourceOnStatus(crc, catalogSourceName, namespace, catalogSourceRegistryPodSynced()) - require.NoError(t, err) - - // Get initial configmap - configMap, err := c.KubernetesInterface().CoreV1().ConfigMaps(namespace).Get(context.Background(), fetchedInitialCatalog.Spec.ConfigMap, metav1.GetOptions{}) - require.NoError(t, err) - - // Update package to point to new csv - manifestsRaw, err := yaml.Marshal(packages) - require.NoError(t, err) - configMap.Data[registry.ConfigMapPackageName] = string(manifestsRaw) - - // Update raw CRDs - var crdsRaw []byte - crdStrings := []string{} - for _, crd := range crds { - crdStrings = append(crdStrings, serializeCRD(crd)) - } - crdsRaw, err = yaml.Marshal(crdStrings) - require.NoError(t, err) - configMap.Data[registry.ConfigMapCRDName] = strings.Replace(string(crdsRaw), "- |\n ", "- ", -1) - - // Update raw CSVs - csvsRaw, err := yaml.Marshal(csvs) - require.NoError(t, err) - configMap.Data[registry.ConfigMapCSVName] = string(csvsRaw) - - // Update configmap - _, err = c.KubernetesInterface().CoreV1().ConfigMaps(namespace).Update(context.Background(), configMap, metav1.UpdateOptions{}) - require.NoError(t, err) - - // wait for catalog to update - var lastState string - lastTime := time.Now() - _, err = fetchCatalogSourceOnStatus(crc, catalogSourceName, namespace, func(catalog *operatorsv1alpha1.CatalogSource) bool { - before := fetchedInitialCatalog.Status.ConfigMapResource - after := catalog.Status.ConfigMapResource - if after != nil && after.LastUpdateTime.After(before.LastUpdateTime.Time) && after.ResourceVersion != before.ResourceVersion && - catalog.Status.GRPCConnectionState.LastConnectTime.After(after.LastUpdateTime.Time) && catalog.Status.GRPCConnectionState.LastObservedState == "READY" { - fmt.Println("catalog updated") - return true - } - if catalog.Status.GRPCConnectionState.LastObservedState != lastState { - 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) - lastState = catalog.Status.GRPCConnectionState.LastObservedState - lastTime = time.Now() - } - return false - }) - require.NoError(t, err) + fetchedInitialCatalog, err := fetchCatalogSourceOnStatus(crc, catalogSourceName, namespace, catalogSourceRegistryPodSynced()) + require.NoError(t, err) + + // Get initial configmap + configMap, err := c.KubernetesInterface().CoreV1().ConfigMaps(namespace).Get(context.Background(), fetchedInitialCatalog.Spec.ConfigMap, metav1.GetOptions{}) + require.NoError(t, err) + + // Update package to point to new csv + manifestsRaw, err := yaml.Marshal(packages) + require.NoError(t, err) + configMap.Data[registry.ConfigMapPackageName] = string(manifestsRaw) + + // Update raw CRDs + var crdsRaw []byte + crdStrings := []string{} + for _, crd := range crds { + crdStrings = append(crdStrings, serializeCRD(crd)) + } + crdsRaw, err = yaml.Marshal(crdStrings) + require.NoError(t, err) + configMap.Data[registry.ConfigMapCRDName] = strings.Replace(string(crdsRaw), "- |\n ", "- ", -1) + + // Update raw CSVs + csvsRaw, err := yaml.Marshal(csvs) + require.NoError(t, err) + configMap.Data[registry.ConfigMapCSVName] = string(csvsRaw) + + // Update configmap + _, err = c.KubernetesInterface().CoreV1().ConfigMaps(namespace).Update(context.Background(), configMap, metav1.UpdateOptions{}) + require.NoError(t, err) + + // wait for catalog to update + var lastState string + lastTime := time.Now() + _, err = fetchCatalogSourceOnStatus(crc, catalogSourceName, namespace, func(catalog *operatorsv1alpha1.CatalogSource) bool { + before := fetchedInitialCatalog.Status.ConfigMapResource + after := catalog.Status.ConfigMapResource + if after != nil && after.LastUpdateTime.After(before.LastUpdateTime.Time) && after.ResourceVersion != before.ResourceVersion && + catalog.Status.GRPCConnectionState.LastConnectTime.After(after.LastUpdateTime.Time) && catalog.Status.GRPCConnectionState.LastObservedState == "READY" { + fmt.Println("catalog updated") + return true + } + if catalog.Status.GRPCConnectionState.LastObservedState != lastState { + 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) + lastState = catalog.Status.GRPCConnectionState.LastObservedState + lastTime = time.Now() + } + return false + }) + require.NoError(t, err) } func subscriptionCurrentCSVGetter(crclient versioned.Interface, namespace, subName string) func() string { - return func() string { - subscription, err := crclient.OperatorsV1alpha1().Subscriptions(namespace).Get(context.Background(), subName, metav1.GetOptions{}) - if err != nil || subscription == nil { - return "" - } - return subscription.Status.CurrentCSV - } + return func() string { + subscription, err := crclient.OperatorsV1alpha1().Subscriptions(namespace).Get(context.Background(), subName, metav1.GetOptions{}) + if err != nil || subscription == nil { + return "" + } + return subscription.Status.CurrentCSV + } } diff --git a/staging/operator-lifecycle-manager/test/e2e/util.go b/staging/operator-lifecycle-manager/test/e2e/util.go index 63d8a253d1..708e062f28 100644 --- a/staging/operator-lifecycle-manager/test/e2e/util.go +++ b/staging/operator-lifecycle-manager/test/e2e/util.go @@ -1,177 +1,177 @@ package e2e import ( - "bytes" - "context" - "fmt" - "os" - "regexp" - "strings" - "time" - - "github.com/ghodss/yaml" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - gtypes "github.com/onsi/gomega/types" - operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" - operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" - "github.com/stretchr/testify/require" - "google.golang.org/grpc" - grpcinsecure "google.golang.org/grpc/credentials/insecure" - "google.golang.org/grpc/health/grpc_health_v1" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - extScheme "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - k8sjson "k8s.io/apimachinery/pkg/runtime/serializer/json" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/apimachinery/pkg/watch" - "k8s.io/apiserver/pkg/storage/names" - "k8s.io/client-go/dynamic" - k8sscheme "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/rest" - k8scontrollerclient "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" - "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/bundle" - "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install" - "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" - controllerclient "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/controller-runtime/client" - "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient" - pmversioned "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/client/clientset/versioned" - "github.com/operator-framework/operator-lifecycle-manager/test/e2e/ctx" + "bytes" + "context" + "fmt" + "os" + "regexp" + "strings" + "time" + + "github.com/ghodss/yaml" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + gtypes "github.com/onsi/gomega/types" + operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" + operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + grpcinsecure "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/health/grpc_health_v1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + extScheme "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + k8sjson "k8s.io/apimachinery/pkg/runtime/serializer/json" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/apiserver/pkg/storage/names" + "k8s.io/client-go/dynamic" + k8sscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + k8scontrollerclient "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/bundle" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" + controllerclient "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/controller-runtime/client" + "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient" + pmversioned "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/client/clientset/versioned" + "github.com/operator-framework/operator-lifecycle-manager/test/e2e/ctx" ) const ( - pollInterval = 100 * time.Millisecond - pollDuration = 5 * time.Minute + pollInterval = 5 * time.Second + pollDuration = 10 * time.Minute - olmConfigMap = "olm-operators" // No-longer used, how long do we keep this around? + olmConfigMap = "olm-operators" // No-longer used, how long do we keep this around? - // sync name with scripts/install_local.sh - packageServerCSV = "packageserver.v1.0.0" + // sync name with scripts/install_local.sh + packageServerCSV = "packageserver.v1.0.0" ) var ( - genName = names.SimpleNameGenerator.GenerateName - nonAlphaNumericRegexp = regexp.MustCompile(`[^a-zA-Z0-9]`) + genName = names.SimpleNameGenerator.GenerateName + nonAlphaNumericRegexp = regexp.MustCompile(`[^a-zA-Z0-9]`) ) // newKubeClient configures a client to talk to the cluster defined by KUBECONFIG func newKubeClient() operatorclient.ClientInterface { - return ctx.Ctx().KubeClient() + return ctx.Ctx().KubeClient() } func newCRClient() versioned.Interface { - return ctx.Ctx().OperatorClient() + return ctx.Ctx().OperatorClient() } func newDynamicClient(t GinkgoTInterface, config *rest.Config) dynamic.Interface { - return ctx.Ctx().DynamicClient() + return ctx.Ctx().DynamicClient() } func newPMClient() pmversioned.Interface { - return ctx.Ctx().PackageClient() + return ctx.Ctx().PackageClient() } // objectRefToNamespacedName is a helper function that's responsible for translating // a *corev1.ObjectReference into a types.NamespacedName. func objectRefToNamespacedName(ip *corev1.ObjectReference) types.NamespacedName { - return types.NamespacedName{ - Name: ip.Name, - Namespace: ip.Namespace, - } + return types.NamespacedName{ + Name: ip.Name, + Namespace: ip.Namespace, + } } // addBundleUnpackTimeoutOGAnnotation is a helper function that's responsible for // adding the "operatorframework.io/bundle-unpack-timeout" annotation to an OperatorGroup // resource. func addBundleUnpackTimeoutOGAnnotation(ctx context.Context, c k8scontrollerclient.Client, ogNN types.NamespacedName, timeout string) { - setOGAnnotation(ctx, c, ogNN, bundle.BundleUnpackTimeoutAnnotationKey, timeout) + setOGAnnotation(ctx, c, ogNN, bundle.BundleUnpackTimeoutAnnotationKey, timeout) } func setBundleUnpackRetryMinimumIntervalAnnotation(ctx context.Context, c k8scontrollerclient.Client, ogNN types.NamespacedName, interval string) { - setOGAnnotation(ctx, c, ogNN, bundle.BundleUnpackRetryMinimumIntervalAnnotationKey, interval) + setOGAnnotation(ctx, c, ogNN, bundle.BundleUnpackRetryMinimumIntervalAnnotationKey, interval) } func setOGAnnotation(ctx context.Context, c k8scontrollerclient.Client, ogNN types.NamespacedName, key, value string) { - Eventually(func() error { - og := &operatorsv1.OperatorGroup{} - if err := c.Get(ctx, ogNN, og); err != nil { - return err - } - annotations := og.GetAnnotations() - if len(value) == 0 { - delete(annotations, key) - } else { - annotations[key] = value - } - og.SetAnnotations(annotations) - - return c.Update(ctx, og) - }).Should(Succeed()) + Eventually(func() error { + og := &operatorsv1.OperatorGroup{} + if err := c.Get(ctx, ogNN, og); err != nil { + return err + } + annotations := og.GetAnnotations() + if len(value) == 0 { + delete(annotations, key) + } else { + annotations[key] = value + } + og.SetAnnotations(annotations) + + return c.Update(ctx, og) + }).Should(Succeed()) } type cleanupFunc func() // waitFor wraps wait.Pool with default polling parameters func waitFor(fn func() (bool, error)) error { - return wait.Poll(pollInterval, pollDuration, func() (bool, error) { - return fn() - }) + return wait.Poll(pollInterval, pollDuration, func() (bool, error) { + return fn() + }) } // awaitPods waits for a set of pods to exist in the cluster func awaitPods(t GinkgoTInterface, c operatorclient.ClientInterface, namespace, selector string, checkPods podsCheckFunc) (*corev1.PodList, error) { - var fetchedPodList *corev1.PodList - var err error + var fetchedPodList *corev1.PodList + var err error - err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { - fetchedPodList, err = c.KubernetesInterface().CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{ - LabelSelector: selector, - }) + err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { + fetchedPodList, err = c.KubernetesInterface().CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{ + LabelSelector: selector, + }) - if err != nil { - return false, err - } + if err != nil { + return false, err + } - t.Logf("Waiting for pods matching selector %s to match given conditions", selector) + t.Logf("Waiting for pods matching selector %s to match given conditions", selector) - return checkPods(fetchedPodList), nil - }) + return checkPods(fetchedPodList), nil + }) - require.NoError(t, err) - return fetchedPodList, err + require.NoError(t, err) + return fetchedPodList, err } func awaitPodsWithInterval(t GinkgoTInterface, c operatorclient.ClientInterface, namespace, selector string, interval time.Duration, - duration time.Duration, checkPods podsCheckFunc) (*corev1.PodList, error) { - var fetchedPodList *corev1.PodList - var err error + duration time.Duration, checkPods podsCheckFunc) (*corev1.PodList, error) { + var fetchedPodList *corev1.PodList + var err error - err = wait.Poll(interval, duration, func() (bool, error) { - fetchedPodList, err = c.KubernetesInterface().CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{ - LabelSelector: selector, - }) + err = wait.Poll(interval, duration, func() (bool, error) { + fetchedPodList, err = c.KubernetesInterface().CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{ + LabelSelector: selector, + }) - if err != nil { - return false, err - } + if err != nil { + return false, err + } - t.Logf("Waiting for pods matching selector %s to match given conditions", selector) + t.Logf("Waiting for pods matching selector %s to match given conditions", selector) - return checkPods(fetchedPodList), nil - }) + return checkPods(fetchedPodList), nil + }) - require.NoError(t, err) - return fetchedPodList, err + require.NoError(t, err) + return fetchedPodList, err } // podsCheckFunc describes a function that true if the given PodList meets some criteria; false otherwise. @@ -179,33 +179,33 @@ type podsCheckFunc func(pods *corev1.PodList) bool // unionPodsCheck returns a podsCheckFunc that represents the union of the given podsCheckFuncs. func unionPodsCheck(checks ...podsCheckFunc) podsCheckFunc { - return func(pods *corev1.PodList) bool { - for _, check := range checks { - if !check(pods) { - return false - } - } - - return true - } + return func(pods *corev1.PodList) bool { + for _, check := range checks { + if !check(pods) { + return false + } + } + + return true + } } // podCount returns a podsCheckFunc that returns true if a PodList is of length count; false otherwise. func podCount(count int) podsCheckFunc { - return func(pods *corev1.PodList) bool { - return len(pods.Items) == count - } + return func(pods *corev1.PodList) bool { + return len(pods.Items) == count + } } // podsReady returns true if all of the pods in the given PodList have a ready condition with ConditionStatus "True"; false otherwise. func podsReady(pods *corev1.PodList) bool { - for _, pod := range pods.Items { - if !podReady(&pod) { - return false - } - } + for _, pod := range pods.Items { + if !podReady(&pod) { + return false + } + } - return true + return true } // podCheckFunc describes a function that returns true if the given Pod meets some criteria; false otherwise. @@ -213,110 +213,110 @@ type podCheckFunc func(pod *corev1.Pod) bool // hasPodIP returns true if the given Pod has a PodIP. func hasPodIP(pod *corev1.Pod) bool { - return pod.Status.PodIP != "" + return pod.Status.PodIP != "" } // podReady returns true if the given Pod has a ready condition with ConditionStatus "True"; false otherwise. func podReady(pod *corev1.Pod) bool { - var status corev1.ConditionStatus - for _, condition := range pod.Status.Conditions { - if condition.Type != corev1.PodReady { - // Ignore all condition other than PodReady - continue - } - - // Found PodReady condition - status = condition.Status - break - } - - return status == corev1.ConditionTrue + var status corev1.ConditionStatus + for _, condition := range pod.Status.Conditions { + if condition.Type != corev1.PodReady { + // Ignore all condition other than PodReady + continue + } + + // Found PodReady condition + status = condition.Status + break + } + + return status == corev1.ConditionTrue } func awaitPod(t GinkgoTInterface, c operatorclient.ClientInterface, namespace, name string, checkPod podCheckFunc) *corev1.Pod { - var pod *corev1.Pod - err := wait.Poll(pollInterval, pollDuration, func() (bool, error) { - p, err := c.KubernetesInterface().CoreV1().Pods(namespace).Get(context.Background(), name, metav1.GetOptions{}) - if err != nil { - return false, err - } - pod = p - return checkPod(pod), nil - }) - require.NoError(t, err) - - return pod + var pod *corev1.Pod + err := wait.Poll(pollInterval, pollDuration, func() (bool, error) { + p, err := c.KubernetesInterface().CoreV1().Pods(namespace).Get(context.Background(), name, metav1.GetOptions{}) + if err != nil { + return false, err + } + pod = p + return checkPod(pod), nil + }) + require.NoError(t, err) + + return pod } func awaitAnnotations(t GinkgoTInterface, query func() (metav1.ObjectMeta, error), expected map[string]string) error { - err := wait.Poll(pollInterval, pollDuration, func() (bool, error) { - t.Logf("Waiting for annotations to match %v", expected) - obj, err := query() - if err != nil && !apierrors.IsNotFound(err) { - return false, err - } - t.Logf("current annotations: %v", obj.GetAnnotations()) - - if len(obj.GetAnnotations()) != len(expected) { - return false, nil - } - - for key, value := range expected { - if v, ok := obj.GetAnnotations()[key]; !ok || v != value { - return false, nil - } - } - - t.Logf("Annotations match") - return true, nil - }) - - return err + err := wait.Poll(pollInterval, pollDuration, func() (bool, error) { + t.Logf("Waiting for annotations to match %v", expected) + obj, err := query() + if err != nil && !apierrors.IsNotFound(err) { + return false, err + } + t.Logf("current annotations: %v", obj.GetAnnotations()) + + if len(obj.GetAnnotations()) != len(expected) { + return false, nil + } + + for key, value := range expected { + if v, ok := obj.GetAnnotations()[key]; !ok || v != value { + return false, nil + } + } + + t.Logf("Annotations match") + return true, nil + }) + + return err } type checkResourceFunc func() error func waitForDelete(checkResource checkResourceFunc) error { - err := wait.Poll(pollInterval, pollDuration, func() (bool, error) { - err := checkResource() - if apierrors.IsNotFound(err) { - return true, nil - } - if err != nil { - return false, err - } - return false, nil - }) - - return err + err := wait.Poll(pollInterval, pollDuration, func() (bool, error) { + err := checkResource() + if apierrors.IsNotFound(err) { + return true, nil + } + if err != nil { + return false, err + } + return false, nil + }) + + return err } func waitForEmptyList(checkList func() (int, error)) error { - err := wait.Poll(pollInterval, pollDuration, func() (bool, error) { - count, err := checkList() - if err != nil { - return false, err - } - if count == 0 { - return true, nil - } - return false, nil - }) - - return err + err := wait.Poll(pollInterval, pollDuration, func() (bool, error) { + count, err := checkList() + if err != nil { + return false, err + } + if count == 0 { + return true, nil + } + return false, nil + }) + + return err } func waitForGVR(dynamicClient dynamic.Interface, gvr schema.GroupVersionResource, name, namespace string) error { - return wait.Poll(pollInterval, pollDuration, func() (bool, error) { - _, err := dynamicClient.Resource(gvr).Namespace(namespace).Get(context.Background(), name, metav1.GetOptions{}) - if err != nil { - if apierrors.IsNotFound(err) { - return false, nil - } - return false, err - } - return true, nil - }) + return wait.Poll(pollInterval, pollDuration, func() (bool, error) { + _, err := dynamicClient.Resource(gvr).Namespace(namespace).Get(context.Background(), name, metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + return false, nil + } + return false, err + } + return true, nil + }) } type catalogSourceCheckFunc func(*operatorsv1alpha1.CatalogSource) bool @@ -326,651 +326,651 @@ type catalogSourceCheckFunc func(*operatorsv1alpha1.CatalogSource) bool var checkPodHealth = false func registryPodHealthy(address string) bool { - if !checkPodHealth { - fmt.Println("skipping health check") - return true - } - - conn, err := grpc.Dial(address, grpc.WithTransportCredentials(grpcinsecure.NewCredentials())) - if err != nil { - fmt.Printf("error connecting: %s\n", err.Error()) - return false - } - health := grpc_health_v1.NewHealthClient(conn) - res, err := health.Check(context.Background(), &grpc_health_v1.HealthCheckRequest{Service: "Registry"}) - if err != nil { - fmt.Printf("error connecting: %s\n", err.Error()) - return false - } - if res.Status != grpc_health_v1.HealthCheckResponse_SERVING { - fmt.Printf("not healthy: %s\n", res.Status.String()) - return false - } - return true + if !checkPodHealth { + fmt.Println("skipping health check") + return true + } + + conn, err := grpc.Dial(address, grpc.WithTransportCredentials(grpcinsecure.NewCredentials())) + if err != nil { + fmt.Printf("error connecting: %s\n", err.Error()) + return false + } + health := grpc_health_v1.NewHealthClient(conn) + res, err := health.Check(context.Background(), &grpc_health_v1.HealthCheckRequest{Service: "Registry"}) + if err != nil { + fmt.Printf("error connecting: %s\n", err.Error()) + return false + } + if res.Status != grpc_health_v1.HealthCheckResponse_SERVING { + fmt.Printf("not healthy: %s\n", res.Status.String()) + return false + } + return true } func catalogSourceRegistryPodSynced() func(catalog *operatorsv1alpha1.CatalogSource) bool { - var lastState string - lastTime := time.Now() - return func(catalog *operatorsv1alpha1.CatalogSource) bool { - registry := catalog.Status.RegistryServiceStatus - connState := catalog.Status.GRPCConnectionState - state := "NO_CONNECTION" - if connState != nil { - state = connState.LastObservedState - } - if state != lastState { - fmt.Printf("waiting %s for catalog pod %s/%s to be available (for sync) - %s\n", time.Since(lastTime), catalog.GetNamespace(), catalog.GetName(), state) - lastState = state - lastTime = time.Now() - } - if registry != nil && connState != nil && !connState.LastConnectTime.IsZero() && connState.LastObservedState == "READY" { - fmt.Printf("probing catalog %s pod with address %s\n", catalog.GetName(), registry.Address()) - return registryPodHealthy(registry.Address()) - } - return false - } + var lastState string + lastTime := time.Now() + return func(catalog *operatorsv1alpha1.CatalogSource) bool { + registry := catalog.Status.RegistryServiceStatus + connState := catalog.Status.GRPCConnectionState + state := "NO_CONNECTION" + if connState != nil { + state = connState.LastObservedState + } + if state != lastState { + fmt.Printf("waiting %s for catalog pod %s/%s to be available (for sync) - %s\n", time.Since(lastTime), catalog.GetNamespace(), catalog.GetName(), state) + lastState = state + lastTime = time.Now() + } + if registry != nil && connState != nil && !connState.LastConnectTime.IsZero() && connState.LastObservedState == "READY" { + fmt.Printf("probing catalog %s pod with address %s\n", catalog.GetName(), registry.Address()) + return registryPodHealthy(registry.Address()) + } + return false + } } func catalogSourceInvalidSpec(catalog *operatorsv1alpha1.CatalogSource) bool { - return catalog.Status.Reason == operatorsv1alpha1.CatalogSourceSpecInvalidError + return catalog.Status.Reason == operatorsv1alpha1.CatalogSourceSpecInvalidError } func fetchCatalogSourceOnStatus(crc versioned.Interface, name, namespace string, check catalogSourceCheckFunc) (*operatorsv1alpha1.CatalogSource, error) { - var fetched *operatorsv1alpha1.CatalogSource - var err error - - err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { - fetched, err = crc.OperatorsV1alpha1().CatalogSources(namespace).Get(context.Background(), name, metav1.GetOptions{}) - if err != nil || fetched == nil { - err = fmt.Errorf("failed to fetch catalogSource %s/%s: %v\n", namespace, name, err) - fmt.Println(err.Error()) - return false, err - } - return check(fetched), nil - }) - - if err != nil { - err = fmt.Errorf("failed to wati for catalog source to reach intended state: %w", err) - } - return fetched, err + var fetched *operatorsv1alpha1.CatalogSource + var err error + + err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { + fetched, err = crc.OperatorsV1alpha1().CatalogSources(namespace).Get(context.Background(), name, metav1.GetOptions{}) + if err != nil || fetched == nil { + err = fmt.Errorf("failed to fetch catalogSource %s/%s: %v\n", namespace, name, err) + fmt.Println(err.Error()) + return false, err + } + return check(fetched), nil + }) + + if err != nil { + err = fmt.Errorf("failed to wati for catalog source to reach intended state: %w", err) + } + return fetched, err } // createFieldNotEqualSelector generates a field selector that matches resources that have a field value that DOES NOT match any of a set of values. // This function panics if the generated selector cannot be parsed. func createFieldNotEqualSelector(field string, values ...string) fields.Selector { - var builder strings.Builder - for i, value := range values { - builder.WriteString(field) - builder.WriteString("!=") - builder.WriteString(value) - if i < len(values)-1 { - builder.WriteString(",") - } - } - - selector, err := fields.ParseSelector(builder.String()) - if err != nil { - panic(fmt.Errorf("failed to build fields-not-equal selector: %s", err)) - } - - return selector + var builder strings.Builder + for i, value := range values { + builder.WriteString(field) + builder.WriteString("!=") + builder.WriteString(value) + if i < len(values)-1 { + builder.WriteString(",") + } + } + + selector, err := fields.ParseSelector(builder.String()) + if err != nil { + panic(fmt.Errorf("failed to build fields-not-equal selector: %s", err)) + } + + return selector } // MaskNotFound "masks" an given error by returning nil when it refers to a "NotFound" API status response, otherwise returns the error unaltered. func MaskNotFound(err error) error { - if apierrors.IsNotFound(err) { - return nil - } + if apierrors.IsNotFound(err) { + return nil + } - return err + return err } var ( - persistentCatalogNames = []string{olmConfigMap} - ephemeralCatalogFieldSelector = k8scontrollerclient.MatchingFieldsSelector{ - Selector: createFieldNotEqualSelector("metadata.name", persistentCatalogNames...), - } - persistentConfigMapNames = []string{olmConfigMap} - ephemeralConfigMapsFieldSelector = k8scontrollerclient.MatchingFieldsSelector{ - Selector: createFieldNotEqualSelector("metadata.name", persistentConfigMapNames...), - } - persistentCSVNames = []string{packageServerCSV} - ephemeralCSVFieldSelector = k8scontrollerclient.MatchingFieldsSelector{ - Selector: createFieldNotEqualSelector("metadata.name", persistentCSVNames...), - } + persistentCatalogNames = []string{olmConfigMap} + ephemeralCatalogFieldSelector = k8scontrollerclient.MatchingFieldsSelector{ + Selector: createFieldNotEqualSelector("metadata.name", persistentCatalogNames...), + } + persistentConfigMapNames = []string{olmConfigMap} + ephemeralConfigMapsFieldSelector = k8scontrollerclient.MatchingFieldsSelector{ + Selector: createFieldNotEqualSelector("metadata.name", persistentConfigMapNames...), + } + persistentCSVNames = []string{packageServerCSV} + ephemeralCSVFieldSelector = k8scontrollerclient.MatchingFieldsSelector{ + Selector: createFieldNotEqualSelector("metadata.name", persistentCSVNames...), + } ) // TearDown deletes all OLM resources in the corresponding namespace and at the cluster scope. func TearDown(namespace string) { - if env := os.Getenv("SKIP_CLEANUP"); env != "" { - fmt.Printf("Skipping cleanup of namespace %s...\n", namespace) - return - } - var ( - clientCtx = context.Background() - client = ctx.Ctx().Client() - dynamic = ctx.Ctx().DynamicClient() - inNamespace = k8scontrollerclient.InNamespace(namespace) - logf = ctx.Ctx().Logf - ) - - // Cleanup non persistent OLM CRs - logf("cleaning up ephemeral test resources...") - - logf("deleting test subscriptions...") - Eventually(func() error { - return client.DeleteAllOf(clientCtx, &operatorsv1alpha1.Subscription{}, inNamespace) - }).Should(Succeed(), "failed to delete test subscriptions") - - var subscriptiongvr = schema.GroupVersionResource{Group: "operators.coreos.com", Version: "v1alpha1", Resource: "subscriptions"} - Eventually(func() ([]unstructured.Unstructured, error) { - list, err := dynamic.Resource(subscriptiongvr).Namespace(namespace).List(context.Background(), metav1.ListOptions{}) - if err != nil { - return nil, err - } - return list.Items, nil - }).Should(BeEmpty(), "failed to await deletion of test subscriptions") - - logf("deleting test installplans...") - Eventually(func() error { - return client.DeleteAllOf(clientCtx, &operatorsv1alpha1.InstallPlan{}, inNamespace) - }).Should(Succeed(), "failed to delete test installplans") - - Eventually(func() (remaining []operatorsv1alpha1.InstallPlan, err error) { - list := &operatorsv1alpha1.InstallPlanList{} - err = client.List(clientCtx, list, inNamespace) - if list != nil { - remaining = list.Items - } - - return - }).Should(BeEmpty(), "failed to await deletion of test installplans") - - logf("deleting test catalogsources...") - Eventually(func() error { - return client.DeleteAllOf(clientCtx, &operatorsv1alpha1.CatalogSource{}, inNamespace, ephemeralCatalogFieldSelector) - }).Should(Succeed(), "failed to delete test catalogsources") - - Eventually(func() (remaining []operatorsv1alpha1.CatalogSource, err error) { - list := &operatorsv1alpha1.CatalogSourceList{} - err = client.List(clientCtx, list, inNamespace, ephemeralCatalogFieldSelector) - if list != nil { - remaining = list.Items - } - - return - }).Should(BeEmpty(), "failed to await deletion of test catalogsources") - - logf("deleting test crds...") - remainingCSVs := func() (csvs []operatorsv1alpha1.ClusterServiceVersion, err error) { - list := &operatorsv1alpha1.ClusterServiceVersionList{} - err = client.List(clientCtx, list, inNamespace, ephemeralCSVFieldSelector) - if list != nil { - csvs = list.Items - } - - return - } - - var crds []apiextensionsv1.CustomResourceDefinition - Eventually(func() error { - csvs, err := remainingCSVs() - if err != nil { - return err - } - - for _, csv := range csvs { - for _, desc := range csv.Spec.CustomResourceDefinitions.Owned { - crd := &apiextensionsv1.CustomResourceDefinition{} - err := client.Get(clientCtx, types.NamespacedName{Name: desc.Name}, crd) - if apierrors.IsNotFound(err) { - continue - } - if err != nil { - return err - } - crds = append(crds, *crd) - } - } - - return nil - }).Should(Succeed(), "failed to aggregate test crds for deletion") - - Eventually(func() error { - for _, crd := range crds { - // Note: NotFound errors will be masked, so we can simply iterate until no other errors are returned. - // This is pretty inefficient, so if we're concerned about the number of API calls, we should replace this with something more sparing. - if err := client.Delete(clientCtx, &crd); MaskNotFound(err) != nil { - return err - } - } - - return nil - }).Should(Succeed(), "failed to delete test crds") - - logf("deleting test csvs...") - Eventually(func() error { - return client.DeleteAllOf(clientCtx, &operatorsv1alpha1.ClusterServiceVersion{}, inNamespace, ephemeralCSVFieldSelector) - }).Should(Succeed(), "failed to delete test csvs") - - Eventually(remainingCSVs).Should(BeEmpty(), "failed to await deletion of test csvs") - - logf("test resources deleted") + if env := os.Getenv("SKIP_CLEANUP"); env != "" { + fmt.Printf("Skipping cleanup of namespace %s...\n", namespace) + return + } + var ( + clientCtx = context.Background() + client = ctx.Ctx().Client() + dynamic = ctx.Ctx().DynamicClient() + inNamespace = k8scontrollerclient.InNamespace(namespace) + logf = ctx.Ctx().Logf + ) + + // Cleanup non persistent OLM CRs + logf("cleaning up ephemeral test resources...") + + logf("deleting test subscriptions...") + Eventually(func() error { + return client.DeleteAllOf(clientCtx, &operatorsv1alpha1.Subscription{}, inNamespace) + }).Should(Succeed(), "failed to delete test subscriptions") + + var subscriptiongvr = schema.GroupVersionResource{Group: "operators.coreos.com", Version: "v1alpha1", Resource: "subscriptions"} + Eventually(func() ([]unstructured.Unstructured, error) { + list, err := dynamic.Resource(subscriptiongvr).Namespace(namespace).List(context.Background(), metav1.ListOptions{}) + if err != nil { + return nil, err + } + return list.Items, nil + }).Should(BeEmpty(), "failed to await deletion of test subscriptions") + + logf("deleting test installplans...") + Eventually(func() error { + return client.DeleteAllOf(clientCtx, &operatorsv1alpha1.InstallPlan{}, inNamespace) + }).Should(Succeed(), "failed to delete test installplans") + + Eventually(func() (remaining []operatorsv1alpha1.InstallPlan, err error) { + list := &operatorsv1alpha1.InstallPlanList{} + err = client.List(clientCtx, list, inNamespace) + if list != nil { + remaining = list.Items + } + + return + }).Should(BeEmpty(), "failed to await deletion of test installplans") + + logf("deleting test catalogsources...") + Eventually(func() error { + return client.DeleteAllOf(clientCtx, &operatorsv1alpha1.CatalogSource{}, inNamespace, ephemeralCatalogFieldSelector) + }).Should(Succeed(), "failed to delete test catalogsources") + + Eventually(func() (remaining []operatorsv1alpha1.CatalogSource, err error) { + list := &operatorsv1alpha1.CatalogSourceList{} + err = client.List(clientCtx, list, inNamespace, ephemeralCatalogFieldSelector) + if list != nil { + remaining = list.Items + } + + return + }).Should(BeEmpty(), "failed to await deletion of test catalogsources") + + logf("deleting test crds...") + remainingCSVs := func() (csvs []operatorsv1alpha1.ClusterServiceVersion, err error) { + list := &operatorsv1alpha1.ClusterServiceVersionList{} + err = client.List(clientCtx, list, inNamespace, ephemeralCSVFieldSelector) + if list != nil { + csvs = list.Items + } + + return + } + + var crds []apiextensionsv1.CustomResourceDefinition + Eventually(func() error { + csvs, err := remainingCSVs() + if err != nil { + return err + } + + for _, csv := range csvs { + for _, desc := range csv.Spec.CustomResourceDefinitions.Owned { + crd := &apiextensionsv1.CustomResourceDefinition{} + err := client.Get(clientCtx, types.NamespacedName{Name: desc.Name}, crd) + if apierrors.IsNotFound(err) { + continue + } + if err != nil { + return err + } + crds = append(crds, *crd) + } + } + + return nil + }).Should(Succeed(), "failed to aggregate test crds for deletion") + + Eventually(func() error { + for _, crd := range crds { + // Note: NotFound errors will be masked, so we can simply iterate until no other errors are returned. + // This is pretty inefficient, so if we're concerned about the number of API calls, we should replace this with something more sparing. + if err := client.Delete(clientCtx, &crd); MaskNotFound(err) != nil { + return err + } + } + + return nil + }).Should(Succeed(), "failed to delete test crds") + + logf("deleting test csvs...") + Eventually(func() error { + return client.DeleteAllOf(clientCtx, &operatorsv1alpha1.ClusterServiceVersion{}, inNamespace, ephemeralCSVFieldSelector) + }).Should(Succeed(), "failed to delete test csvs") + + Eventually(remainingCSVs).Should(BeEmpty(), "failed to await deletion of test csvs") + + logf("test resources deleted") } func buildCatalogSourceCleanupFunc(c operatorclient.ClientInterface, crc versioned.Interface, namespace string, catalogSource *operatorsv1alpha1.CatalogSource) cleanupFunc { - return func() { - if env := os.Getenv("SKIP_CLEANUP"); env != "" { - fmt.Printf("Skipping cleanup of CatalogSource %s/%s...\n", namespace, catalogSource.GetName()) - return - } - ctx.Ctx().Logf("Deleting catalog source %s...", catalogSource.GetName()) - err := crc.OperatorsV1alpha1().CatalogSources(namespace).Delete(context.Background(), catalogSource.GetName(), metav1.DeleteOptions{}) - if err != nil && !apierrors.IsNotFound(err) { - Expect(err).ToNot(HaveOccurred()) - } - - Eventually(func() (bool, error) { - listOpts := metav1.ListOptions{ - LabelSelector: "olm.catalogSource=" + catalogSource.GetName(), - FieldSelector: "status.phase=Running", - } - fetched, err := c.KubernetesInterface().CoreV1().Pods(catalogSource.GetNamespace()).List(context.Background(), listOpts) - if err != nil { - return false, err - } - if len(fetched.Items) == 0 { - return true, nil - } - ctx.Ctx().Logf("waiting for the catalog source %s pod to be deleted...", fetched.Items[0].GetName()) - return false, nil - }).Should(BeTrue()) - } + return func() { + if env := os.Getenv("SKIP_CLEANUP"); env != "" { + fmt.Printf("Skipping cleanup of CatalogSource %s/%s...\n", namespace, catalogSource.GetName()) + return + } + ctx.Ctx().Logf("Deleting catalog source %s...", catalogSource.GetName()) + err := crc.OperatorsV1alpha1().CatalogSources(namespace).Delete(context.Background(), catalogSource.GetName(), metav1.DeleteOptions{}) + if err != nil && !apierrors.IsNotFound(err) { + Expect(err).ToNot(HaveOccurred()) + } + + Eventually(func() (bool, error) { + listOpts := metav1.ListOptions{ + LabelSelector: "olm.catalogSource=" + catalogSource.GetName(), + FieldSelector: "status.phase=Running", + } + fetched, err := c.KubernetesInterface().CoreV1().Pods(catalogSource.GetNamespace()).List(context.Background(), listOpts) + if err != nil { + return false, err + } + if len(fetched.Items) == 0 { + return true, nil + } + ctx.Ctx().Logf("waiting for the catalog source %s pod to be deleted...", fetched.Items[0].GetName()) + return false, nil + }).Should(BeTrue()) + } } func buildConfigMapCleanupFunc(c operatorclient.ClientInterface, namespace string, configMap *corev1.ConfigMap) cleanupFunc { - return func() { - if env := os.Getenv("SKIP_CLEANUP"); env != "" { - fmt.Printf("Skipping cleanup of ConfigMap %s/%s...\n", namespace, configMap.GetName()) - return - } - ctx.Ctx().Logf("Deleting config map %s...", configMap.GetName()) - err := c.KubernetesInterface().CoreV1().ConfigMaps(namespace).Delete(context.Background(), configMap.GetName(), metav1.DeleteOptions{}) - if err != nil && !apierrors.IsNotFound(err) { - Expect(err).ToNot(HaveOccurred()) - } - } + return func() { + if env := os.Getenv("SKIP_CLEANUP"); env != "" { + fmt.Printf("Skipping cleanup of ConfigMap %s/%s...\n", namespace, configMap.GetName()) + return + } + ctx.Ctx().Logf("Deleting config map %s...", configMap.GetName()) + err := c.KubernetesInterface().CoreV1().ConfigMaps(namespace).Delete(context.Background(), configMap.GetName(), metav1.DeleteOptions{}) + if err != nil && !apierrors.IsNotFound(err) { + Expect(err).ToNot(HaveOccurred()) + } + } } func buildServiceAccountCleanupFunc(t GinkgoTInterface, c operatorclient.ClientInterface, namespace string, serviceAccount *corev1.ServiceAccount) cleanupFunc { - return func() { - if env := os.Getenv("SKIP_CLEANUP"); env != "" { - fmt.Printf("Skipping cleanup of ServiceAccount %s/%s...\n", namespace, serviceAccount.GetName()) - return - } - t.Logf("Deleting service account %s...", serviceAccount.GetName()) - require.NoError(t, c.KubernetesInterface().CoreV1().ServiceAccounts(namespace).Delete(context.Background(), serviceAccount.GetName(), metav1.DeleteOptions{})) - } + return func() { + if env := os.Getenv("SKIP_CLEANUP"); env != "" { + fmt.Printf("Skipping cleanup of ServiceAccount %s/%s...\n", namespace, serviceAccount.GetName()) + return + } + t.Logf("Deleting service account %s...", serviceAccount.GetName()) + require.NoError(t, c.KubernetesInterface().CoreV1().ServiceAccounts(namespace).Delete(context.Background(), serviceAccount.GetName(), metav1.DeleteOptions{})) + } } func createInvalidGRPCCatalogSource(c operatorclient.ClientInterface, crc versioned.Interface, name, namespace string) (*operatorsv1alpha1.CatalogSource, cleanupFunc) { - catalogSource := &operatorsv1alpha1.CatalogSource{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.CatalogSourceKind, - APIVersion: operatorsv1alpha1.CatalogSourceCRDAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: operatorsv1alpha1.CatalogSourceSpec{ - SourceType: "grpc", - Image: "localhost:0/not/exists:catsrc", - GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ - SecurityContextConfig: operatorsv1alpha1.Restricted, - }, - }, - } - - ctx.Ctx().Logf("Creating catalog source %s in namespace %s...", name, namespace) - catalogSource, err := crc.OperatorsV1alpha1().CatalogSources(namespace).Create(context.Background(), catalogSource, metav1.CreateOptions{}) - Expect(err).ToNot(HaveOccurred()) - ctx.Ctx().Logf("Catalog source %s created", name) - return catalogSource, buildCatalogSourceCleanupFunc(c, crc, namespace, catalogSource) + catalogSource := &operatorsv1alpha1.CatalogSource{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.CatalogSourceKind, + APIVersion: operatorsv1alpha1.CatalogSourceCRDAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: operatorsv1alpha1.CatalogSourceSpec{ + SourceType: "grpc", + Image: "localhost:0/not/exists:catsrc", + GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ + SecurityContextConfig: operatorsv1alpha1.Restricted, + }, + }, + } + + ctx.Ctx().Logf("Creating catalog source %s in namespace %s...", name, namespace) + catalogSource, err := crc.OperatorsV1alpha1().CatalogSources(namespace).Create(context.Background(), catalogSource, metav1.CreateOptions{}) + Expect(err).ToNot(HaveOccurred()) + ctx.Ctx().Logf("Catalog source %s created", name) + return catalogSource, buildCatalogSourceCleanupFunc(c, crc, namespace, catalogSource) } func createInternalCatalogSource( - c operatorclient.ClientInterface, - crc versioned.Interface, - name, - namespace string, - manifests []registry.PackageManifest, - crds []apiextensionsv1.CustomResourceDefinition, - csvs []operatorsv1alpha1.ClusterServiceVersion, + c operatorclient.ClientInterface, + crc versioned.Interface, + name, + namespace string, + manifests []registry.PackageManifest, + crds []apiextensionsv1.CustomResourceDefinition, + csvs []operatorsv1alpha1.ClusterServiceVersion, ) (*operatorsv1alpha1.CatalogSource, cleanupFunc) { - configMap, configMapCleanup := createConfigMapForCatalogData(c, name, namespace, manifests, crds, csvs) - - // Create an internal CatalogSource custom resource pointing to the ConfigMap - catalogSource := &operatorsv1alpha1.CatalogSource{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.CatalogSourceKind, - APIVersion: operatorsv1alpha1.CatalogSourceCRDAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: operatorsv1alpha1.CatalogSourceSpec{ - SourceType: "internal", - ConfigMap: configMap.GetName(), - GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ - SecurityContextConfig: operatorsv1alpha1.Restricted, - }, - }, - } - - ctx.Ctx().Logf("Creating catalog source %s in namespace %s...", name, namespace) - catalogSource, err := crc.OperatorsV1alpha1().CatalogSources(namespace).Create(context.Background(), catalogSource, metav1.CreateOptions{}) - if err != nil && !apierrors.IsAlreadyExists(err) { - Expect(err).ToNot(HaveOccurred()) - } - ctx.Ctx().Logf("Catalog source %s created", name) - - cleanupInternalCatalogSource := func() { - ctx.Ctx().Logf("Cleaning catalog source %s", name) - configMapCleanup() - buildCatalogSourceCleanupFunc(c, crc, namespace, catalogSource)() - ctx.Ctx().Logf("Done cleaning catalog source %s", name) - } - return catalogSource, cleanupInternalCatalogSource + configMap, configMapCleanup := createConfigMapForCatalogData(c, name, namespace, manifests, crds, csvs) + + // Create an internal CatalogSource custom resource pointing to the ConfigMap + catalogSource := &operatorsv1alpha1.CatalogSource{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.CatalogSourceKind, + APIVersion: operatorsv1alpha1.CatalogSourceCRDAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: operatorsv1alpha1.CatalogSourceSpec{ + SourceType: "internal", + ConfigMap: configMap.GetName(), + GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ + SecurityContextConfig: operatorsv1alpha1.Restricted, + }, + }, + } + + ctx.Ctx().Logf("Creating catalog source %s in namespace %s...", name, namespace) + catalogSource, err := crc.OperatorsV1alpha1().CatalogSources(namespace).Create(context.Background(), catalogSource, metav1.CreateOptions{}) + if err != nil && !apierrors.IsAlreadyExists(err) { + Expect(err).ToNot(HaveOccurred()) + } + ctx.Ctx().Logf("Catalog source %s created", name) + + cleanupInternalCatalogSource := func() { + ctx.Ctx().Logf("Cleaning catalog source %s", name) + configMapCleanup() + buildCatalogSourceCleanupFunc(c, crc, namespace, catalogSource)() + ctx.Ctx().Logf("Done cleaning catalog source %s", name) + } + return catalogSource, cleanupInternalCatalogSource } func createInternalCatalogSourceWithPriority(c operatorclient.ClientInterface, - crc versioned.Interface, - name, - namespace string, - manifests []registry.PackageManifest, - crds []apiextensionsv1.CustomResourceDefinition, - csvs []operatorsv1alpha1.ClusterServiceVersion, - priority int, + crc versioned.Interface, + name, + namespace string, + manifests []registry.PackageManifest, + crds []apiextensionsv1.CustomResourceDefinition, + csvs []operatorsv1alpha1.ClusterServiceVersion, + priority int, ) (*operatorsv1alpha1.CatalogSource, cleanupFunc) { - configMap, configMapCleanup := createConfigMapForCatalogData(c, name, namespace, manifests, crds, csvs) - // Create an internal CatalogSource custom resource pointing to the ConfigMap - catalogSource := &operatorsv1alpha1.CatalogSource{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.CatalogSourceKind, - APIVersion: operatorsv1alpha1.CatalogSourceCRDAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: operatorsv1alpha1.CatalogSourceSpec{ - SourceType: "internal", - ConfigMap: configMap.GetName(), - Priority: priority, - GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ - SecurityContextConfig: operatorsv1alpha1.Restricted, - }, - }, - } - catalogSource.SetNamespace(namespace) - - ctx.Ctx().Logf("Creating catalog source %s in namespace %s...", name, namespace) - catalogSource, err := crc.OperatorsV1alpha1().CatalogSources(namespace).Create(context.Background(), catalogSource, metav1.CreateOptions{}) - if err != nil && !apierrors.IsAlreadyExists(err) { - Expect(err).ToNot(HaveOccurred()) - } - ctx.Ctx().Logf("Catalog source %s created", name) - - cleanupInternalCatalogSource := func() { - configMapCleanup() - buildCatalogSourceCleanupFunc(c, crc, namespace, catalogSource)() - } - return catalogSource, cleanupInternalCatalogSource + configMap, configMapCleanup := createConfigMapForCatalogData(c, name, namespace, manifests, crds, csvs) + // Create an internal CatalogSource custom resource pointing to the ConfigMap + catalogSource := &operatorsv1alpha1.CatalogSource{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.CatalogSourceKind, + APIVersion: operatorsv1alpha1.CatalogSourceCRDAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: operatorsv1alpha1.CatalogSourceSpec{ + SourceType: "internal", + ConfigMap: configMap.GetName(), + Priority: priority, + GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ + SecurityContextConfig: operatorsv1alpha1.Restricted, + }, + }, + } + catalogSource.SetNamespace(namespace) + + ctx.Ctx().Logf("Creating catalog source %s in namespace %s...", name, namespace) + catalogSource, err := crc.OperatorsV1alpha1().CatalogSources(namespace).Create(context.Background(), catalogSource, metav1.CreateOptions{}) + if err != nil && !apierrors.IsAlreadyExists(err) { + Expect(err).ToNot(HaveOccurred()) + } + ctx.Ctx().Logf("Catalog source %s created", name) + + cleanupInternalCatalogSource := func() { + configMapCleanup() + buildCatalogSourceCleanupFunc(c, crc, namespace, catalogSource)() + } + return catalogSource, cleanupInternalCatalogSource } func createV1CRDInternalCatalogSource( - t GinkgoTInterface, - c operatorclient.ClientInterface, - crc versioned.Interface, - name, - namespace string, - manifests []registry.PackageManifest, - crds []apiextensionsv1.CustomResourceDefinition, - csvs []operatorsv1alpha1.ClusterServiceVersion, + t GinkgoTInterface, + c operatorclient.ClientInterface, + crc versioned.Interface, + name, + namespace string, + manifests []registry.PackageManifest, + crds []apiextensionsv1.CustomResourceDefinition, + csvs []operatorsv1alpha1.ClusterServiceVersion, ) (*operatorsv1alpha1.CatalogSource, cleanupFunc) { - configMap, configMapCleanup := createV1CRDConfigMapForCatalogData(t, c, name, namespace, manifests, crds, csvs) - - // Create an internal CatalogSource custom resource pointing to the ConfigMap - catalogSource := &operatorsv1alpha1.CatalogSource{ - TypeMeta: metav1.TypeMeta{ - Kind: operatorsv1alpha1.CatalogSourceKind, - APIVersion: operatorsv1alpha1.CatalogSourceCRDAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: operatorsv1alpha1.CatalogSourceSpec{ - SourceType: "internal", - ConfigMap: configMap.GetName(), - GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ - SecurityContextConfig: operatorsv1alpha1.Restricted, - }, - }, - } - catalogSource.SetNamespace(namespace) - - ctx.Ctx().Logf("Creating catalog source %s in namespace %s...", name, namespace) - catalogSource, err := crc.OperatorsV1alpha1().CatalogSources(namespace).Create(context.Background(), catalogSource, metav1.CreateOptions{}) - if err != nil && !apierrors.IsAlreadyExists(err) { - require.NoError(t, err) - } - ctx.Ctx().Logf("Catalog source %s created", name) - - cleanupInternalCatalogSource := func() { - configMapCleanup() - buildCatalogSourceCleanupFunc(c, crc, namespace, catalogSource)() - } - return catalogSource, cleanupInternalCatalogSource + configMap, configMapCleanup := createV1CRDConfigMapForCatalogData(t, c, name, namespace, manifests, crds, csvs) + + // Create an internal CatalogSource custom resource pointing to the ConfigMap + catalogSource := &operatorsv1alpha1.CatalogSource{ + TypeMeta: metav1.TypeMeta{ + Kind: operatorsv1alpha1.CatalogSourceKind, + APIVersion: operatorsv1alpha1.CatalogSourceCRDAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: operatorsv1alpha1.CatalogSourceSpec{ + SourceType: "internal", + ConfigMap: configMap.GetName(), + GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ + SecurityContextConfig: operatorsv1alpha1.Restricted, + }, + }, + } + catalogSource.SetNamespace(namespace) + + ctx.Ctx().Logf("Creating catalog source %s in namespace %s...", name, namespace) + catalogSource, err := crc.OperatorsV1alpha1().CatalogSources(namespace).Create(context.Background(), catalogSource, metav1.CreateOptions{}) + if err != nil && !apierrors.IsAlreadyExists(err) { + require.NoError(t, err) + } + ctx.Ctx().Logf("Catalog source %s created", name) + + cleanupInternalCatalogSource := func() { + configMapCleanup() + buildCatalogSourceCleanupFunc(c, crc, namespace, catalogSource)() + } + return catalogSource, cleanupInternalCatalogSource } func createConfigMapForCatalogData( - c operatorclient.ClientInterface, - name, - namespace string, - manifests []registry.PackageManifest, - crds []apiextensionsv1.CustomResourceDefinition, - csvs []operatorsv1alpha1.ClusterServiceVersion, + c operatorclient.ClientInterface, + name, + namespace string, + manifests []registry.PackageManifest, + crds []apiextensionsv1.CustomResourceDefinition, + csvs []operatorsv1alpha1.ClusterServiceVersion, ) (*corev1.ConfigMap, cleanupFunc) { - // Create a config map containing the PackageManifests and CSVs - configMapName := fmt.Sprintf("%s-configmap", name) - catalogConfigMap := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: configMapName, - Namespace: namespace, - Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, - }, - Data: map[string]string{}, - } - catalogConfigMap.SetNamespace(namespace) - - // Add raw manifests - if manifests != nil { - manifestsRaw, err := yaml.Marshal(manifests) - Expect(err).ToNot(HaveOccurred()) - catalogConfigMap.Data[registry.ConfigMapPackageName] = string(manifestsRaw) - } - - // Add raw CRDs - var crdsRaw []byte - if crds != nil { - crdStrings := []string{} - for _, crd := range crds { - crdStrings = append(crdStrings, serializeCRD(crd)) - } - var err error - crdsRaw, err = yaml.Marshal(crdStrings) - Expect(err).ToNot(HaveOccurred()) - } - catalogConfigMap.Data[registry.ConfigMapCRDName] = strings.Replace(string(crdsRaw), "- |\n ", "- ", -1) - - // Add raw CSVs - if csvs != nil { - csvsRaw, err := yaml.Marshal(csvs) - Expect(err).ToNot(HaveOccurred()) - catalogConfigMap.Data[registry.ConfigMapCSVName] = string(csvsRaw) - } - - createdConfigMap, err := c.KubernetesInterface().CoreV1().ConfigMaps(namespace).Create(context.Background(), catalogConfigMap, metav1.CreateOptions{}) - if err != nil && !apierrors.IsAlreadyExists(err) { - Expect(err).ToNot(HaveOccurred()) - } - return createdConfigMap, buildConfigMapCleanupFunc(c, namespace, createdConfigMap) + // Create a config map containing the PackageManifests and CSVs + configMapName := fmt.Sprintf("%s-configmap", name) + catalogConfigMap := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: configMapName, + Namespace: namespace, + Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, + }, + Data: map[string]string{}, + } + catalogConfigMap.SetNamespace(namespace) + + // Add raw manifests + if manifests != nil { + manifestsRaw, err := yaml.Marshal(manifests) + Expect(err).ToNot(HaveOccurred()) + catalogConfigMap.Data[registry.ConfigMapPackageName] = string(manifestsRaw) + } + + // Add raw CRDs + var crdsRaw []byte + if crds != nil { + crdStrings := []string{} + for _, crd := range crds { + crdStrings = append(crdStrings, serializeCRD(crd)) + } + var err error + crdsRaw, err = yaml.Marshal(crdStrings) + Expect(err).ToNot(HaveOccurred()) + } + catalogConfigMap.Data[registry.ConfigMapCRDName] = strings.Replace(string(crdsRaw), "- |\n ", "- ", -1) + + // Add raw CSVs + if csvs != nil { + csvsRaw, err := yaml.Marshal(csvs) + Expect(err).ToNot(HaveOccurred()) + catalogConfigMap.Data[registry.ConfigMapCSVName] = string(csvsRaw) + } + + createdConfigMap, err := c.KubernetesInterface().CoreV1().ConfigMaps(namespace).Create(context.Background(), catalogConfigMap, metav1.CreateOptions{}) + if err != nil && !apierrors.IsAlreadyExists(err) { + Expect(err).ToNot(HaveOccurred()) + } + return createdConfigMap, buildConfigMapCleanupFunc(c, namespace, createdConfigMap) } func createV1CRDConfigMapForCatalogData( - t GinkgoTInterface, - c operatorclient.ClientInterface, - name, - namespace string, - manifests []registry.PackageManifest, - crds []apiextensionsv1.CustomResourceDefinition, - csvs []operatorsv1alpha1.ClusterServiceVersion, + t GinkgoTInterface, + c operatorclient.ClientInterface, + name, + namespace string, + manifests []registry.PackageManifest, + crds []apiextensionsv1.CustomResourceDefinition, + csvs []operatorsv1alpha1.ClusterServiceVersion, ) (*corev1.ConfigMap, cleanupFunc) { - // Create a config map containing the PackageManifests and CSVs - configMapName := fmt.Sprintf("%s-configmap", name) - catalogConfigMap := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: configMapName, - Namespace: namespace, - Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, - }, - Data: map[string]string{}, - } - catalogConfigMap.SetNamespace(namespace) - - // Add raw manifests - if manifests != nil { - manifestsRaw, err := yaml.Marshal(manifests) - require.NoError(t, err) - catalogConfigMap.Data[registry.ConfigMapPackageName] = string(manifestsRaw) - } - - // Add raw CRDs - var crdsRaw []byte - if crds != nil { - crdStrings := []string{} - for _, crd := range crds { - crdStrings = append(crdStrings, serializeV1CRD(t, &crd)) - } - var err error - crdsRaw, err = yaml.Marshal(crdStrings) - require.NoError(t, err) - } - catalogConfigMap.Data[registry.ConfigMapCRDName] = strings.Replace(string(crdsRaw), "- |\n ", "- ", -1) - - // Add raw CSVs - if csvs != nil { - csvsRaw, err := yaml.Marshal(csvs) - require.NoError(t, err) - catalogConfigMap.Data[registry.ConfigMapCSVName] = string(csvsRaw) - } - - createdConfigMap, err := c.KubernetesInterface().CoreV1().ConfigMaps(namespace).Create(context.Background(), catalogConfigMap, metav1.CreateOptions{}) - if err != nil && !apierrors.IsAlreadyExists(err) { - require.NoError(t, err) - } - return createdConfigMap, buildConfigMapCleanupFunc(c, namespace, createdConfigMap) + // Create a config map containing the PackageManifests and CSVs + configMapName := fmt.Sprintf("%s-configmap", name) + catalogConfigMap := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: configMapName, + Namespace: namespace, + Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, + }, + Data: map[string]string{}, + } + catalogConfigMap.SetNamespace(namespace) + + // Add raw manifests + if manifests != nil { + manifestsRaw, err := yaml.Marshal(manifests) + require.NoError(t, err) + catalogConfigMap.Data[registry.ConfigMapPackageName] = string(manifestsRaw) + } + + // Add raw CRDs + var crdsRaw []byte + if crds != nil { + crdStrings := []string{} + for _, crd := range crds { + crdStrings = append(crdStrings, serializeV1CRD(t, &crd)) + } + var err error + crdsRaw, err = yaml.Marshal(crdStrings) + require.NoError(t, err) + } + catalogConfigMap.Data[registry.ConfigMapCRDName] = strings.Replace(string(crdsRaw), "- |\n ", "- ", -1) + + // Add raw CSVs + if csvs != nil { + csvsRaw, err := yaml.Marshal(csvs) + require.NoError(t, err) + catalogConfigMap.Data[registry.ConfigMapCSVName] = string(csvsRaw) + } + + createdConfigMap, err := c.KubernetesInterface().CoreV1().ConfigMaps(namespace).Create(context.Background(), catalogConfigMap, metav1.CreateOptions{}) + if err != nil && !apierrors.IsAlreadyExists(err) { + require.NoError(t, err) + } + return createdConfigMap, buildConfigMapCleanupFunc(c, namespace, createdConfigMap) } func serializeCRD(crd apiextensionsv1.CustomResourceDefinition) string { - scheme := runtime.NewScheme() - - Expect(extScheme.AddToScheme(scheme)).Should(Succeed()) - Expect(k8sscheme.AddToScheme(scheme)).Should(Succeed()) - Expect(apiextensionsv1.AddToScheme(scheme)).Should(Succeed()) - - out := &apiextensionsv1.CustomResourceDefinition{} - Expect(scheme.Convert(&crd, out, nil)).To(Succeed()) - out.TypeMeta = metav1.TypeMeta{ - Kind: "CustomResourceDefinition", - APIVersion: "apiextensions.k8s.io/v1", - } - - // set up object serializer - serializer := k8sjson.NewYAMLSerializer(k8sjson.DefaultMetaFactory, scheme, scheme) - - // create an object manifest - var manifest bytes.Buffer - Expect(serializer.Encode(out, &manifest)).To(Succeed()) - return manifest.String() + scheme := runtime.NewScheme() + + Expect(extScheme.AddToScheme(scheme)).Should(Succeed()) + Expect(k8sscheme.AddToScheme(scheme)).Should(Succeed()) + Expect(apiextensionsv1.AddToScheme(scheme)).Should(Succeed()) + + out := &apiextensionsv1.CustomResourceDefinition{} + Expect(scheme.Convert(&crd, out, nil)).To(Succeed()) + out.TypeMeta = metav1.TypeMeta{ + Kind: "CustomResourceDefinition", + APIVersion: "apiextensions.k8s.io/v1", + } + + // set up object serializer + serializer := k8sjson.NewYAMLSerializer(k8sjson.DefaultMetaFactory, scheme, scheme) + + // create an object manifest + var manifest bytes.Buffer + Expect(serializer.Encode(out, &manifest)).To(Succeed()) + return manifest.String() } func serializeV1CRD(t GinkgoTInterface, crd *apiextensionsv1.CustomResourceDefinition) string { - scheme := runtime.NewScheme() - require.NoError(t, apiextensionsv1.AddToScheme(scheme)) + scheme := runtime.NewScheme() + require.NoError(t, apiextensionsv1.AddToScheme(scheme)) - // set up object serializer - serializer := k8sjson.NewYAMLSerializer(k8sjson.DefaultMetaFactory, scheme, scheme) + // set up object serializer + serializer := k8sjson.NewYAMLSerializer(k8sjson.DefaultMetaFactory, scheme, scheme) - // create an object manifest - var manifest bytes.Buffer - require.NoError(t, serializer.Encode(crd, &manifest)) - return manifest.String() + // create an object manifest + var manifest bytes.Buffer + require.NoError(t, serializer.Encode(crd, &manifest)) + return manifest.String() } func createCR(c operatorclient.ClientInterface, item *unstructured.Unstructured, apiGroup, version, namespace, resourceKind, resourceName string) (cleanupFunc, error) { - err := c.CreateCustomResource(item) - if err != nil { - return nil, err - } - return buildCRCleanupFunc(c, apiGroup, version, namespace, resourceKind, resourceName), nil + err := c.CreateCustomResource(item) + if err != nil { + return nil, err + } + return buildCRCleanupFunc(c, apiGroup, version, namespace, resourceKind, resourceName), nil } func buildCRCleanupFunc(c operatorclient.ClientInterface, apiGroup, version, namespace, resourceKind, resourceName string) cleanupFunc { - return func() { - if env := os.Getenv("SKIP_CLEANUP"); env != "" { - fmt.Printf("Skipping cleanup of custom resource %s.%s/%s %s/%s...\n", apiGroup, resourceKind, version, namespace, resourceName) - return - } - err := c.DeleteCustomResource(apiGroup, version, namespace, resourceKind, resourceName) - if err != nil { - fmt.Println(err) - } - - waitForDelete(func() error { - _, err := c.GetCustomResource(apiGroup, version, namespace, resourceKind, resourceName) - return err - }) - } + return func() { + if env := os.Getenv("SKIP_CLEANUP"); env != "" { + fmt.Printf("Skipping cleanup of custom resource %s.%s/%s %s/%s...\n", apiGroup, resourceKind, version, namespace, resourceName) + return + } + err := c.DeleteCustomResource(apiGroup, version, namespace, resourceKind, resourceName) + if err != nil { + fmt.Println(err) + } + + waitForDelete(func() error { + _, err := c.GetCustomResource(apiGroup, version, namespace, resourceKind, resourceName) + return err + }) + } } // Local determines whether test is running locally or in a container on openshift-CI. // Queries for a clusterversion object specific to OpenShift. func Local(client operatorclient.ClientInterface) (bool, error) { - const ClusterVersionGroup = "config.openshift.io" - const ClusterVersionVersion = "v1" - const ClusterVersionKind = "ClusterVersion" - gv := metav1.GroupVersion{Group: ClusterVersionGroup, Version: ClusterVersionVersion}.String() - - groups, err := client.KubernetesInterface().Discovery().ServerResourcesForGroupVersion(gv) - if err != nil { - if apierrors.IsNotFound(err) { - return true, nil - } - return true, fmt.Errorf("checking if cluster is local: checking server groups: %s", err) - } - - for _, group := range groups.APIResources { - if group.Kind == ClusterVersionKind { - return false, nil - } - } - - return true, nil + const ClusterVersionGroup = "config.openshift.io" + const ClusterVersionVersion = "v1" + const ClusterVersionKind = "ClusterVersion" + gv := metav1.GroupVersion{Group: ClusterVersionGroup, Version: ClusterVersionVersion}.String() + + groups, err := client.KubernetesInterface().Discovery().ServerResourcesForGroupVersion(gv) + if err != nil { + if apierrors.IsNotFound(err) { + return true, nil + } + return true, fmt.Errorf("checking if cluster is local: checking server groups: %s", err) + } + + for _, group := range groups.APIResources { + if group.Kind == ClusterVersionKind { + return false, nil + } + } + + return true, nil } // predicateFunc is a predicate for watch events. @@ -978,195 +978,195 @@ type predicateFunc func(event watch.Event) (met bool) // awaitPredicates waits for all predicates to be met by events of a watch in the order given. func awaitPredicates(ctx context.Context, w watch.Interface, fns ...predicateFunc) { - if len(fns) < 1 { - panic("no predicates given to await") - } - - i := 0 - for i < len(fns) { - select { - case <-ctx.Done(): - Expect(ctx.Err()).ToNot(HaveOccurred()) - return - case event, ok := <-w.ResultChan(): - if !ok { - return - } - - if fns[i](event) { - i++ - } - } - } + if len(fns) < 1 { + panic("no predicates given to await") + } + + i := 0 + for i < len(fns) { + select { + case <-ctx.Done(): + Expect(ctx.Err()).ToNot(HaveOccurred()) + return + case event, ok := <-w.ResultChan(): + if !ok { + return + } + + if fns[i](event) { + i++ + } + } + } } // filteredPredicate filters events to the given predicate by event type to the given types. // When no event types are given as arguments, all event types are passed through. func filteredPredicate(fn predicateFunc, eventTypes ...watch.EventType) predicateFunc { - return func(event watch.Event) bool { - valid := true - for _, eventType := range eventTypes { - if valid = eventType == event.Type; valid { - break - } - } - - if !valid { - return false - } - - return fn(event) - } + return func(event watch.Event) bool { + valid := true + for _, eventType := range eventTypes { + if valid = eventType == event.Type; valid { + break + } + } + + if !valid { + return false + } + + return fn(event) + } } func deploymentPredicate(fn func(*appsv1.Deployment) bool) predicateFunc { - return func(event watch.Event) bool { - deployment, ok := event.Object.(*appsv1.Deployment) - Expect(ok).To(BeTrue(), "unexpected event object type %T in deployment", event.Object) + return func(event watch.Event) bool { + deployment, ok := event.Object.(*appsv1.Deployment) + Expect(ok).To(BeTrue(), "unexpected event object type %T in deployment", event.Object) - return fn(deployment) - } + return fn(deployment) + } } var deploymentAvailable = filteredPredicate(deploymentPredicate(func(deployment *appsv1.Deployment) bool { - for _, condition := range deployment.Status.Conditions { - if condition.Type == appsv1.DeploymentAvailable && condition.Status == corev1.ConditionTrue { - return true - } - } + for _, condition := range deployment.Status.Conditions { + if condition.Type == appsv1.DeploymentAvailable && condition.Status == corev1.ConditionTrue { + return true + } + } - return false + return false }), watch.Added, watch.Modified) func deploymentReplicas(replicas int32) predicateFunc { - return filteredPredicate(deploymentPredicate(func(deployment *appsv1.Deployment) bool { - return deployment.Status.Replicas == replicas - }), watch.Added, watch.Modified) + return filteredPredicate(deploymentPredicate(func(deployment *appsv1.Deployment) bool { + return deployment.Status.Replicas == replicas + }), watch.Added, watch.Modified) } func Apply(obj controllerclient.Object, changeFunc interface{}) func() error { - return ctx.Ctx().SSAClient().Apply(context.Background(), obj, changeFunc) + return ctx.Ctx().SSAClient().Apply(context.Background(), obj, changeFunc) } func HavePhase(goal operatorsv1alpha1.InstallPlanPhase) gtypes.GomegaMatcher { - return WithTransform(func(plan *operatorsv1alpha1.InstallPlan) operatorsv1alpha1.InstallPlanPhase { - return plan.Status.Phase - }, Equal(goal)) + return WithTransform(func(plan *operatorsv1alpha1.InstallPlan) operatorsv1alpha1.InstallPlanPhase { + return plan.Status.Phase + }, Equal(goal)) } func CSVHasPhase(goal operatorsv1alpha1.ClusterServiceVersionPhase) gtypes.GomegaMatcher { - return WithTransform(func(csv *operatorsv1alpha1.ClusterServiceVersion) operatorsv1alpha1.ClusterServiceVersionPhase { - return csv.Status.Phase - }, Equal(goal)) + return WithTransform(func(csv *operatorsv1alpha1.ClusterServiceVersion) operatorsv1alpha1.ClusterServiceVersionPhase { + return csv.Status.Phase + }, Equal(goal)) } func HaveMessage(goal string) gtypes.GomegaMatcher { - return WithTransform(func(plan *operatorsv1alpha1.InstallPlan) string { - return plan.Status.Message - }, ContainSubstring(goal)) + return WithTransform(func(plan *operatorsv1alpha1.InstallPlan) string { + return plan.Status.Message + }, ContainSubstring(goal)) } func SetupGeneratedTestNamespaceWithOperatorGroup(name string, og operatorsv1.OperatorGroup) corev1.Namespace { - ns := corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - } - Eventually(func() error { - return ctx.Ctx().E2EClient().Create(context.Background(), &ns) - }).Should(Succeed()) + ns := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } + Eventually(func() error { + return ctx.Ctx().E2EClient().Create(context.Background(), &ns) + }).Should(Succeed()) - ctx.Ctx().Logf("created the %s testing namespace", ns.GetName()) + ctx.Ctx().Logf("created the %s testing namespace", ns.GetName()) - Eventually(func() error { - return ctx.Ctx().E2EClient().Create(context.Background(), &og) - }).Should(Succeed()) + Eventually(func() error { + return ctx.Ctx().E2EClient().Create(context.Background(), &og) + }).Should(Succeed()) - ctx.Ctx().Logf("created the %s/%s operator group", og.Namespace, og.Name) + ctx.Ctx().Logf("created the %s/%s operator group", og.Namespace, og.Name) - return ns + return ns } func SetupGeneratedTestNamespace(name string, targetNamespaces ...string) corev1.Namespace { - og := operatorsv1.OperatorGroup{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-operatorgroup", name), - Namespace: name, - }, - Spec: operatorsv1.OperatorGroupSpec{ - TargetNamespaces: targetNamespaces, - }, - } - - return SetupGeneratedTestNamespaceWithOperatorGroup(name, og) + og := operatorsv1.OperatorGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-operatorgroup", name), + Namespace: name, + }, + Spec: operatorsv1.OperatorGroupSpec{ + TargetNamespaces: targetNamespaces, + }, + } + + return SetupGeneratedTestNamespaceWithOperatorGroup(name, og) } func TeardownNamespace(ns string) { - if env := os.Getenv("SKIP_CLEANUP"); env != "" { - fmt.Printf("Skipping cleanup of namespace %s...\n", ns) - return - } - log := ctx.Ctx().Logf - - currentTest := CurrentSpecReport() - if currentTest.Failed() { - log("collecting the %s namespace artifacts as the '%s' test case failed", ns, currentTest.LeafNodeText) - if err := ctx.Ctx().DumpNamespaceArtifacts(ns); err != nil { - log("failed to collect namespace artifacts: %v", err) - } - } - - log("tearing down the %s namespace", ns) - Eventually(func() error { - return ctx.Ctx().E2EClient().Reset() - }).Should(Succeed()) + if env := os.Getenv("SKIP_CLEANUP"); env != "" { + fmt.Printf("Skipping cleanup of namespace %s...\n", ns) + return + } + log := ctx.Ctx().Logf + + currentTest := CurrentSpecReport() + if currentTest.Failed() { + log("collecting the %s namespace artifacts as the '%s' test case failed", ns, currentTest.LeafNodeText) + if err := ctx.Ctx().DumpNamespaceArtifacts(ns); err != nil { + log("failed to collect namespace artifacts: %v", err) + } + } + + log("tearing down the %s namespace", ns) + Eventually(func() error { + return ctx.Ctx().E2EClient().Reset() + }).Should(Succeed()) } func inKind(client operatorclient.ClientInterface) (bool, error) { - nodes, err := client.KubernetesInterface().CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{}) - if err != nil { - // error finding nodes - return false, err - } - for _, node := range nodes.Items { - if !strings.HasPrefix(node.GetName(), "kind-") { - continue - } - if !strings.HasSuffix(node.GetName(), "-control-plane") { - continue - } - return true, nil - } - return false, nil + nodes, err := client.KubernetesInterface().CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{}) + if err != nil { + // error finding nodes + return false, err + } + for _, node := range nodes.Items { + if !strings.HasPrefix(node.GetName(), "kind-") { + continue + } + if !strings.HasSuffix(node.GetName(), "-control-plane") { + continue + } + return true, nil + } + return false, nil } func K8sSafeCurrentTestDescription() string { - return nonAlphaNumericRegexp.ReplaceAllString(CurrentSpecReport().LeafNodeText, "") + return nonAlphaNumericRegexp.ReplaceAllString(CurrentSpecReport().LeafNodeText, "") } func newTokenSecret(client operatorclient.ClientInterface, namespace, saName string) (se *corev1.Secret, cleanup cleanupFunc) { - seName := saName + "-token" - secret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: seName, - Namespace: namespace, - Annotations: map[string]string{corev1.ServiceAccountNameKey: saName}, - }, - Type: corev1.SecretTypeServiceAccountToken, - } - - se, err := client.KubernetesInterface().CoreV1().Secrets(namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) - Expect(err).ToNot(HaveOccurred()) - Expect(se).ToNot(BeNil()) - - cleanup = func() { - if env := os.Getenv("SKIP_CLEANUP"); env != "" { - fmt.Printf("Skipping cleanup of secret %s/%s...\n", namespace, se.GetName()) - return - } - err := client.KubernetesInterface().CoreV1().Secrets(namespace).Delete(context.TODO(), se.GetName(), metav1.DeleteOptions{}) - Expect(err).ToNot(HaveOccurred()) - } - - return se, cleanup + seName := saName + "-token" + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: seName, + Namespace: namespace, + Annotations: map[string]string{corev1.ServiceAccountNameKey: saName}, + }, + Type: corev1.SecretTypeServiceAccountToken, + } + + se, err := client.KubernetesInterface().CoreV1().Secrets(namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) + Expect(err).ToNot(HaveOccurred()) + Expect(se).ToNot(BeNil()) + + cleanup = func() { + if env := os.Getenv("SKIP_CLEANUP"); env != "" { + fmt.Printf("Skipping cleanup of secret %s/%s...\n", namespace, se.GetName()) + return + } + err := client.KubernetesInterface().CoreV1().Secrets(namespace).Delete(context.TODO(), se.GetName(), metav1.DeleteOptions{}) + Expect(err).ToNot(HaveOccurred()) + } + + return se, cleanup } From 32a505a17ee031e869e704755ba4f8e968889d39 Mon Sep 17 00:00:00 2001 From: Per Goncalves da Silva Date: Fri, 5 Sep 2025 13:19:59 +0200 Subject: [PATCH 6/6] update manifests Signed-off-by: Per Goncalves da Silva --- ..._50_olm_00-clusterserviceversions.crd.yaml | 24 +++++++++---------- ..._50_olm_00-clusterserviceversions.crd.yaml | 24 +++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/manifests/0000_50_olm_00-clusterserviceversions.crd.yaml b/manifests/0000_50_olm_00-clusterserviceversions.crd.yaml index 60272595a7..219fae5f0a 100644 --- a/manifests/0000_50_olm_00-clusterserviceversions.crd.yaml +++ b/manifests/0000_50_olm_00-clusterserviceversions.crd.yaml @@ -98,7 +98,7 @@ spec: path: type: string value: - description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + description: RawMessage is a raw encoded JSON value. It implements [Marshaler] and [Unmarshaler] and can be used to delay JSON decoding or precompute a JSON encoding. type: string format: byte x-descriptors: @@ -154,7 +154,7 @@ spec: path: type: string value: - description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + description: RawMessage is a raw encoded JSON value. It implements [Marshaler] and [Unmarshaler] and can be used to delay JSON decoding or precompute a JSON encoding. type: string format: byte x-descriptors: @@ -176,7 +176,7 @@ spec: path: type: string value: - description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + description: RawMessage is a raw encoded JSON value. It implements [Marshaler] and [Unmarshaler] and can be used to delay JSON decoding or precompute a JSON encoding. type: string format: byte x-descriptors: @@ -211,7 +211,7 @@ spec: path: type: string value: - description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + description: RawMessage is a raw encoded JSON value. It implements [Marshaler] and [Unmarshaler] and can be used to delay JSON decoding or precompute a JSON encoding. type: string format: byte x-descriptors: @@ -267,7 +267,7 @@ spec: path: type: string value: - description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + description: RawMessage is a raw encoded JSON value. It implements [Marshaler] and [Unmarshaler] and can be used to delay JSON decoding or precompute a JSON encoding. type: string format: byte x-descriptors: @@ -289,7 +289,7 @@ spec: path: type: string value: - description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + description: RawMessage is a raw encoded JSON value. It implements [Marshaler] and [Unmarshaler] and can be used to delay JSON decoding or precompute a JSON encoding. type: string format: byte x-descriptors: @@ -335,7 +335,7 @@ spec: path: type: string value: - description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + description: RawMessage is a raw encoded JSON value. It implements [Marshaler] and [Unmarshaler] and can be used to delay JSON decoding or precompute a JSON encoding. type: string format: byte x-descriptors: @@ -384,7 +384,7 @@ spec: path: type: string value: - description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + description: RawMessage is a raw encoded JSON value. It implements [Marshaler] and [Unmarshaler] and can be used to delay JSON decoding or precompute a JSON encoding. type: string format: byte x-descriptors: @@ -406,7 +406,7 @@ spec: path: type: string value: - description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + description: RawMessage is a raw encoded JSON value. It implements [Marshaler] and [Unmarshaler] and can be used to delay JSON decoding or precompute a JSON encoding. type: string format: byte x-descriptors: @@ -440,7 +440,7 @@ spec: path: type: string value: - description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + description: RawMessage is a raw encoded JSON value. It implements [Marshaler] and [Unmarshaler] and can be used to delay JSON decoding or precompute a JSON encoding. type: string format: byte x-descriptors: @@ -489,7 +489,7 @@ spec: path: type: string value: - description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + description: RawMessage is a raw encoded JSON value. It implements [Marshaler] and [Unmarshaler] and can be used to delay JSON decoding or precompute a JSON encoding. type: string format: byte x-descriptors: @@ -511,7 +511,7 @@ spec: path: type: string value: - description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + description: RawMessage is a raw encoded JSON value. It implements [Marshaler] and [Unmarshaler] and can be used to delay JSON decoding or precompute a JSON encoding. type: string format: byte x-descriptors: diff --git a/microshift-manifests/0000_50_olm_00-clusterserviceversions.crd.yaml b/microshift-manifests/0000_50_olm_00-clusterserviceversions.crd.yaml index 60272595a7..219fae5f0a 100644 --- a/microshift-manifests/0000_50_olm_00-clusterserviceversions.crd.yaml +++ b/microshift-manifests/0000_50_olm_00-clusterserviceversions.crd.yaml @@ -98,7 +98,7 @@ spec: path: type: string value: - description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + description: RawMessage is a raw encoded JSON value. It implements [Marshaler] and [Unmarshaler] and can be used to delay JSON decoding or precompute a JSON encoding. type: string format: byte x-descriptors: @@ -154,7 +154,7 @@ spec: path: type: string value: - description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + description: RawMessage is a raw encoded JSON value. It implements [Marshaler] and [Unmarshaler] and can be used to delay JSON decoding or precompute a JSON encoding. type: string format: byte x-descriptors: @@ -176,7 +176,7 @@ spec: path: type: string value: - description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + description: RawMessage is a raw encoded JSON value. It implements [Marshaler] and [Unmarshaler] and can be used to delay JSON decoding or precompute a JSON encoding. type: string format: byte x-descriptors: @@ -211,7 +211,7 @@ spec: path: type: string value: - description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + description: RawMessage is a raw encoded JSON value. It implements [Marshaler] and [Unmarshaler] and can be used to delay JSON decoding or precompute a JSON encoding. type: string format: byte x-descriptors: @@ -267,7 +267,7 @@ spec: path: type: string value: - description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + description: RawMessage is a raw encoded JSON value. It implements [Marshaler] and [Unmarshaler] and can be used to delay JSON decoding or precompute a JSON encoding. type: string format: byte x-descriptors: @@ -289,7 +289,7 @@ spec: path: type: string value: - description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + description: RawMessage is a raw encoded JSON value. It implements [Marshaler] and [Unmarshaler] and can be used to delay JSON decoding or precompute a JSON encoding. type: string format: byte x-descriptors: @@ -335,7 +335,7 @@ spec: path: type: string value: - description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + description: RawMessage is a raw encoded JSON value. It implements [Marshaler] and [Unmarshaler] and can be used to delay JSON decoding or precompute a JSON encoding. type: string format: byte x-descriptors: @@ -384,7 +384,7 @@ spec: path: type: string value: - description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + description: RawMessage is a raw encoded JSON value. It implements [Marshaler] and [Unmarshaler] and can be used to delay JSON decoding or precompute a JSON encoding. type: string format: byte x-descriptors: @@ -406,7 +406,7 @@ spec: path: type: string value: - description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + description: RawMessage is a raw encoded JSON value. It implements [Marshaler] and [Unmarshaler] and can be used to delay JSON decoding or precompute a JSON encoding. type: string format: byte x-descriptors: @@ -440,7 +440,7 @@ spec: path: type: string value: - description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + description: RawMessage is a raw encoded JSON value. It implements [Marshaler] and [Unmarshaler] and can be used to delay JSON decoding or precompute a JSON encoding. type: string format: byte x-descriptors: @@ -489,7 +489,7 @@ spec: path: type: string value: - description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + description: RawMessage is a raw encoded JSON value. It implements [Marshaler] and [Unmarshaler] and can be used to delay JSON decoding or precompute a JSON encoding. type: string format: byte x-descriptors: @@ -511,7 +511,7 @@ spec: path: type: string value: - description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + description: RawMessage is a raw encoded JSON value. It implements [Marshaler] and [Unmarshaler] and can be used to delay JSON decoding or precompute a JSON encoding. type: string format: byte x-descriptors: