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

     1  package e2e
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"time"
     8  
     9  	. "github.com/onsi/ginkgo/v2"
    10  	. "github.com/onsi/gomega"
    11  	"github.com/onsi/gomega/format"
    12  	gomegatypes "github.com/onsi/gomega/types"
    13  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
    14  	"github.com/stretchr/testify/require"
    15  	corev1 "k8s.io/api/core/v1"
    16  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    17  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    18  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    19  	"k8s.io/apimachinery/pkg/runtime"
    20  	"k8s.io/apimachinery/pkg/types"
    21  	"k8s.io/apimachinery/pkg/watch"
    22  	"k8s.io/client-go/tools/reference"
    23  	controllerclient "sigs.k8s.io/controller-runtime/pkg/client"
    24  
    25  	operatorsv1 "github.com/operator-framework/api/pkg/operators/v1"
    26  	operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
    27  	clientv1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned/typed/operators/v1"
    28  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/decorators"
    29  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/testobj"
    30  	"github.com/operator-framework/operator-lifecycle-manager/test/e2e/ctx"
    31  )
    32  
    33  // Describes test specs for the Operator resource.
    34  var _ = Describe("Operator API", func() {
    35  	var (
    36  		clientCtx       context.Context
    37  		scheme          *runtime.Scheme
    38  		listOpts        metav1.ListOptions
    39  		operatorClient  clientv1.OperatorInterface
    40  		client          controllerclient.Client
    41  		operatorFactory decorators.OperatorFactory
    42  	)
    43  
    44  	BeforeEach(func() {
    45  		// Setup common utilities
    46  		clientCtx = context.Background()
    47  		scheme = ctx.Ctx().Scheme()
    48  		listOpts = metav1.ListOptions{}
    49  		operatorClient = ctx.Ctx().OperatorClient().OperatorsV1().Operators()
    50  		client = ctx.Ctx().Client()
    51  
    52  		var err error
    53  		operatorFactory, err = decorators.NewSchemedOperatorFactory(scheme)
    54  		Expect(err).ToNot(HaveOccurred())
    55  	})
    56  
    57  	// Ensures that an Operator resource can select its components by label and surface them correctly in its status.
    58  	//
    59  	// Steps:
    60  	// 1. Create an Operator resource, o
    61  	// 2. Ensure o's status eventually contains its component label selector
    62  	// 3. Create namespaces ns-a and ns-b
    63  	// 4. Label ns-a with o's component label
    64  	// 5. Ensure o's status.components.refs field eventually contains a reference to ns-a
    65  	// 6. Create ServiceAccounts sa-a and sa-b in namespaces ns-a and ns-b respectively
    66  	// 7. Label sa-a and sa-b with o's component label
    67  	// 8. Ensure o's status.components.refs field eventually contains references to sa-a and sa-b
    68  	// 9. Remove the component label from sa-b
    69  	// 10. Ensure the reference to sa-b is eventually removed from o's status.components.refs field
    70  	// 11. Delete o
    71  	// 12. Ensure o is re-created
    72  	// 13. Delete ns-a
    73  	// 14. Ensure the reference to ns-a is eventually removed from o's status.components.refs field
    74  	// 15. Delete o
    75  	// 16. Ensure o is not re-created
    76  	// issue: https://github.com/operator-framework/operator-lifecycle-manager/issues/2628
    77  	It("should surface components in its status", func() {
    78  		o := &operatorsv1.Operator{}
    79  		o.SetName(genName("o-"))
    80  		By(fmt.Sprintf("Creating an Operator resource %s", o.GetName()))
    81  
    82  		Consistently(o).ShouldNot(ContainCopiedCSVReferences())
    83  
    84  		Eventually(func() error {
    85  			return client.Create(clientCtx, o)
    86  		}).Should(Succeed())
    87  
    88  		defer func() {
    89  			Eventually(func() error {
    90  				if env := os.Getenv("SKIP_CLEANUP"); env != "" {
    91  					fmt.Printf("Skipping cleanup of operator %s...\n", o.GetName())
    92  					return nil
    93  				}
    94  				err := client.Delete(clientCtx, o)
    95  				if apierrors.IsNotFound(err) {
    96  					return nil
    97  				}
    98  
    99  				return err
   100  			}).Should(Succeed())
   101  		}()
   102  
   103  		By("eventually having a status that contains its component label selector")
   104  		w, err := operatorClient.Watch(clientCtx, listOpts)
   105  		Expect(err).ToNot(HaveOccurred())
   106  		defer w.Stop()
   107  
   108  		deadline, cancel := context.WithTimeout(clientCtx, 1*time.Minute)
   109  		defer cancel()
   110  
   111  		expectedKey := "operators.coreos.com/" + o.GetName()
   112  		awaitPredicates(deadline, w, operatorPredicate(func(op *operatorsv1.Operator) bool {
   113  			if op.Status.Components == nil || op.Status.Components.LabelSelector == nil {
   114  				return false
   115  			}
   116  
   117  			for _, requirement := range op.Status.Components.LabelSelector.MatchExpressions {
   118  				if requirement.Key == expectedKey && requirement.Operator == metav1.LabelSelectorOpExists {
   119  					return true
   120  				}
   121  			}
   122  
   123  			return false
   124  		}))
   125  		defer w.Stop()
   126  
   127  		nsA := &corev1.Namespace{}
   128  		nsA.SetName(genName("ns-a-"))
   129  		nsB := &corev1.Namespace{}
   130  		nsB.SetName(genName("ns-b-"))
   131  		By(fmt.Sprintf("Create namespaces ns-a: (%s) and ns-b: (%s)", nsA.GetName(), nsB.GetName()))
   132  
   133  		for _, ns := range []*corev1.Namespace{nsA, nsB} {
   134  			Eventually(func() error {
   135  				return client.Create(clientCtx, ns)
   136  			}).Should(Succeed())
   137  
   138  			defer func(n *corev1.Namespace) {
   139  				if env := os.Getenv("SKIP_CLEANUP"); env != "" {
   140  					fmt.Printf("Skipping cleanup of namespace %s...\n", n.GetName())
   141  					return
   142  				}
   143  				Eventually(func() error {
   144  					err := client.Delete(clientCtx, n)
   145  					if apierrors.IsNotFound(err) {
   146  						return nil
   147  					}
   148  					return err
   149  				}).Should(Succeed())
   150  			}(ns)
   151  		}
   152  
   153  		By(fmt.Sprintf("Label ns-a (%s) with o's (%s) component label (%s)", nsA.GetName(), o.GetName(), expectedKey))
   154  		setComponentLabel := func(m metav1.Object) error {
   155  			m.SetLabels(map[string]string{
   156  				install.OLMManagedLabelKey: install.OLMManagedLabelValue,
   157  				expectedKey:                "",
   158  			})
   159  			return nil
   160  		}
   161  		Eventually(Apply(nsA, setComponentLabel)).Should(Succeed())
   162  
   163  		By("Ensure o's status.components.refs field eventually contains a reference to ns-a")
   164  		By("eventually listing a single component reference")
   165  		componentRefEventuallyExists(w, true, getReference(scheme, nsA))
   166  
   167  		saA := &corev1.ServiceAccount{}
   168  		saA.SetName(genName("sa-a-"))
   169  		saA.SetNamespace(nsA.GetName())
   170  		saB := &corev1.ServiceAccount{}
   171  		saB.SetName(genName("sa-b-"))
   172  		saB.SetNamespace(nsB.GetName())
   173  		By(fmt.Sprintf("Create ServiceAccounts sa-a (%s/%s) and sa-b (%s/%s) in namespaces ns-a and ns-b respectively", saA.GetNamespace(), saA.GetName(), saB.GetNamespace(), saB.GetName()))
   174  
   175  		for _, sa := range []*corev1.ServiceAccount{saA, saB} {
   176  			Eventually(func() error {
   177  				return client.Create(clientCtx, sa)
   178  			}).Should(Succeed())
   179  			defer func(sa *corev1.ServiceAccount) {
   180  				Eventually(func() error {
   181  					if env := os.Getenv("SKIP_CLEANUP"); env != "" {
   182  						fmt.Printf("Skipping cleanup of serviceaccount %s/%s...\n", sa.GetNamespace(), sa.GetName())
   183  						return nil
   184  					}
   185  					err := client.Delete(clientCtx, sa)
   186  					if apierrors.IsNotFound(err) {
   187  						return nil
   188  					}
   189  					return err
   190  				}).Should(Succeed())
   191  			}(sa)
   192  		}
   193  
   194  		By("Label sa-a and sa-b with o's component label")
   195  		Eventually(Apply(saA, setComponentLabel)).Should(Succeed())
   196  		Eventually(Apply(saB, setComponentLabel)).Should(Succeed())
   197  
   198  		By("Ensure o's status.components.refs field eventually contains references to sa-a and sa-b")
   199  		By("eventually listing multiple component references")
   200  		componentRefEventuallyExists(w, true, getReference(scheme, saA))
   201  		componentRefEventuallyExists(w, true, getReference(scheme, saB))
   202  
   203  		By("Remove the component label from sa-b")
   204  		Eventually(Apply(saB, func(m metav1.Object) error {
   205  			m.SetLabels(nil)
   206  			return nil
   207  		})).Should(Succeed())
   208  
   209  		By("Ensure the reference to sa-b is eventually removed from o's status.components.refs field")
   210  		By("removing a component's reference when it no longer bears the component label")
   211  		componentRefEventuallyExists(w, false, getReference(scheme, saB))
   212  
   213  		By("Delete o")
   214  		Eventually(func() error {
   215  			err := client.Delete(clientCtx, o)
   216  			if err != nil && !apierrors.IsNotFound(err) {
   217  				return err
   218  			}
   219  			return nil
   220  		}).Should(Succeed())
   221  
   222  		By("Ensure that o is eventually recreated (because some of its components still exist).")
   223  		By("recreating the Operator when any components still exist")
   224  		Eventually(func() error {
   225  			return client.Get(clientCtx, types.NamespacedName{Name: o.GetName()}, o)
   226  		}).Should(Succeed())
   227  
   228  		By("Delete ns-a")
   229  		Eventually(func() error {
   230  			err := client.Delete(clientCtx, nsA)
   231  			if apierrors.IsNotFound(err) {
   232  				return nil
   233  			}
   234  			return err
   235  		}).Should(Succeed())
   236  
   237  		By("Ensure the reference to ns-a is eventually removed from o's status.components.refs field")
   238  		By("removing a component's reference when it no longer exists")
   239  		componentRefEventuallyExists(w, false, getReference(scheme, nsA))
   240  
   241  		By("Delete o")
   242  		Eventually(func() error {
   243  			err := client.Delete(clientCtx, o)
   244  			if apierrors.IsNotFound(err) {
   245  				return nil
   246  			}
   247  			return err
   248  		}).Should(Succeed())
   249  
   250  		By("Ensure that o is consistently not found")
   251  		By("verifying the Operator is permanently deleted if it has no components")
   252  		Consistently(func() error {
   253  			err := client.Get(clientCtx, types.NamespacedName{Name: o.GetName()}, o)
   254  			if apierrors.IsNotFound(err) {
   255  				return nil
   256  			}
   257  			return err
   258  		}).Should(Succeed())
   259  	})
   260  
   261  	Context("when a subscription to a package exists", func() {
   262  		var (
   263  			ns           *corev1.Namespace
   264  			sub          *operatorsv1alpha1.Subscription
   265  			ip           *operatorsv1alpha1.InstallPlan
   266  			operatorName types.NamespacedName
   267  		)
   268  
   269  		BeforeEach(func() {
   270  			By("Subscribe to a package and await a successful install")
   271  			ns = &corev1.Namespace{}
   272  			ns.SetName(genName("ns-"))
   273  			Eventually(func() error {
   274  				return client.Create(clientCtx, ns)
   275  			}).Should(Succeed())
   276  			By(fmt.Sprintf("created namespace %s", ns.Name))
   277  
   278  			By("Default to AllNamespaces")
   279  			og := &operatorsv1.OperatorGroup{}
   280  			og.SetNamespace(ns.GetName())
   281  			og.SetName(genName("og-"))
   282  			Eventually(func() error {
   283  				return client.Create(clientCtx, og)
   284  			}).Should(Succeed())
   285  			By(fmt.Sprintf("created operator group %s/%s", og.Namespace, og.Name))
   286  
   287  			cs := &operatorsv1alpha1.CatalogSource{
   288  				Spec: operatorsv1alpha1.CatalogSourceSpec{
   289  					SourceType: operatorsv1alpha1.SourceTypeGrpc,
   290  					Image:      "quay.io/operator-framework/ci-index:latest",
   291  					GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{
   292  						SecurityContextConfig: operatorsv1alpha1.Restricted,
   293  					},
   294  				},
   295  			}
   296  			cs.SetNamespace(ns.GetName())
   297  			cs.SetName(genName("cs-"))
   298  			Eventually(func() error {
   299  				return client.Create(clientCtx, cs)
   300  			}).Should(Succeed())
   301  			By(fmt.Sprintf("created catalog source %s/%s", cs.Namespace, cs.Name))
   302  
   303  			By("Wait for the CatalogSource to be ready")
   304  			_, err := fetchCatalogSourceOnStatus(newCRClient(), cs.GetName(), cs.GetNamespace(), catalogSourceRegistryPodSynced())
   305  			Expect(err).ToNot(HaveOccurred())
   306  
   307  			sub = &operatorsv1alpha1.Subscription{
   308  				Spec: &operatorsv1alpha1.SubscriptionSpec{
   309  					CatalogSource:          cs.GetName(),
   310  					CatalogSourceNamespace: cs.GetNamespace(),
   311  					Package:                "kiali",
   312  					Channel:                "stable",
   313  					InstallPlanApproval:    operatorsv1alpha1.ApprovalAutomatic,
   314  				},
   315  			}
   316  			sub.SetNamespace(cs.GetNamespace())
   317  			sub.SetName(genName("sub-"))
   318  			Eventually(func() error {
   319  				return client.Create(clientCtx, sub)
   320  			}).Should(Succeed())
   321  			By(fmt.Sprintf("created subscription %s/%s", sub.Namespace, sub.Name))
   322  
   323  			_, err = fetchSubscription(newCRClient(), sub.Namespace, sub.Name, subscriptionStateAtLatestChecker())
   324  			require.NoError(GinkgoT(), err)
   325  
   326  			subscriptionWithInstallPLan, err := fetchSubscription(newCRClient(), sub.Namespace, sub.Name, subscriptionHasInstallPlanChecker())
   327  			require.NoError(GinkgoT(), err)
   328  			require.NotNil(GinkgoT(), subscriptionWithInstallPLan)
   329  			ipRef := subscriptionWithInstallPLan.Status.InstallPlanRef
   330  
   331  			ip, err = fetchInstallPlan(GinkgoT(), newCRClient(), ipRef.Name, ipRef.Namespace, buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete))
   332  			Expect(err).To(BeNil())
   333  
   334  			operator, err := operatorFactory.NewPackageOperator(sub.Spec.Package, sub.GetNamespace())
   335  			Expect(err).ToNot(HaveOccurred())
   336  			operatorName = testobj.NamespacedName(operator)
   337  			By(fmt.Sprintf("waiting for operator %s/%s to exist", operator.Namespace, operator.Name))
   338  		})
   339  
   340  		AfterEach(func() {
   341  			Eventually(func() error {
   342  				if env := os.Getenv("SKIP_CLEANUP"); env != "" {
   343  					fmt.Printf("Skipping cleanup of namespace %s...\n", ns.Name)
   344  					return nil
   345  				}
   346  				err := client.Delete(clientCtx, ns)
   347  				if apierrors.IsNotFound(err) {
   348  					return nil
   349  				}
   350  				return err
   351  			}).Should(Succeed())
   352  		})
   353  
   354  		It("should automatically adopt components", func() {
   355  			Consistently(func() (*operatorsv1.Operator, error) {
   356  				o := &operatorsv1.Operator{}
   357  				err := client.Get(clientCtx, operatorName, o)
   358  				return o, err
   359  			}).ShouldNot(ContainCopiedCSVReferences())
   360  
   361  			Eventually(func() (*operatorsv1.Operator, error) {
   362  				o := &operatorsv1.Operator{}
   363  				err := client.Get(clientCtx, operatorName, o)
   364  				return o, err
   365  			}).Should(ReferenceComponents([]*corev1.ObjectReference{
   366  				getReference(scheme, sub),
   367  				getReference(scheme, ip),
   368  				getReference(scheme, testobj.WithNamespacedName(
   369  					&types.NamespacedName{Namespace: sub.GetNamespace(), Name: "kiali-operator.v1.4.2"},
   370  					&operatorsv1alpha1.ClusterServiceVersion{},
   371  				)),
   372  				getReference(scheme, testobj.WithNamespacedName(
   373  					&types.NamespacedName{Namespace: sub.GetNamespace(), Name: "kiali-operator"},
   374  					&corev1.ServiceAccount{},
   375  				)),
   376  				getReference(scheme, testobj.WithName("kialis.kiali.io", &apiextensionsv1.CustomResourceDefinition{})),
   377  				getReference(scheme, testobj.WithName("monitoringdashboards.monitoring.kiali.io", &apiextensionsv1.CustomResourceDefinition{})),
   378  			}))
   379  		})
   380  
   381  		Context("when a namespace is added", func() {
   382  
   383  			var newNs *corev1.Namespace
   384  
   385  			BeforeEach(func() {
   386  				By("Subscribe to a package and await a successful install")
   387  				newNs = &corev1.Namespace{}
   388  				newNs.SetName(genName("ns-"))
   389  				Eventually(func() error {
   390  					return client.Create(clientCtx, newNs)
   391  				}).Should(Succeed())
   392  			})
   393  
   394  			AfterEach(func() {
   395  				Eventually(func() error {
   396  					err := client.Delete(clientCtx, newNs)
   397  					if apierrors.IsNotFound(err) {
   398  						return nil
   399  					}
   400  					return err
   401  				}).Should(Succeed())
   402  			})
   403  
   404  			It("should not adopt copied csvs", func() {
   405  				Consistently(func() (*operatorsv1.Operator, error) {
   406  					o := &operatorsv1.Operator{}
   407  					err := client.Get(clientCtx, operatorName, o)
   408  					return o, err
   409  				}).ShouldNot(ContainCopiedCSVReferences())
   410  			})
   411  		})
   412  	})
   413  })
   414  
   415  func getReference(scheme *runtime.Scheme, obj runtime.Object) *corev1.ObjectReference {
   416  	ref, err := reference.GetReference(scheme, obj)
   417  	if err != nil {
   418  		panic(fmt.Sprintf("unable to get object reference: %s", err))
   419  	}
   420  	ref.UID = ""
   421  	ref.ResourceVersion = ""
   422  
   423  	return ref
   424  }
   425  
   426  func componentRefEventuallyExists(w watch.Interface, exists bool, ref *corev1.ObjectReference) {
   427  	deadline, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
   428  	defer cancel()
   429  
   430  	awaitPredicates(deadline, w, operatorPredicate(func(op *operatorsv1.Operator) bool {
   431  		if op.Status.Components == nil {
   432  			return false
   433  		}
   434  
   435  		for _, r := range op.Status.Components.Refs {
   436  			if r.APIVersion == ref.APIVersion && r.Kind == ref.Kind && r.Namespace == ref.Namespace && r.Name == ref.Name {
   437  				return exists
   438  			}
   439  		}
   440  
   441  		return !exists
   442  	}))
   443  }
   444  
   445  func ContainCopiedCSVReferences() gomegatypes.GomegaMatcher {
   446  	return &copiedCSVRefMatcher{}
   447  }
   448  
   449  type copiedCSVRefMatcher struct {
   450  }
   451  
   452  func (matcher *copiedCSVRefMatcher) Match(actual interface{}) (success bool, err error) {
   453  	if actual == nil {
   454  		return false, nil
   455  	}
   456  	operator, ok := actual.(*operatorsv1.Operator)
   457  	if !ok {
   458  		return false, fmt.Errorf("copiedCSVRefMatcher matcher expects an *Operator")
   459  	}
   460  	if operator.Status.Components == nil {
   461  		return false, nil
   462  	}
   463  	for _, ref := range operator.Status.Components.Refs {
   464  		if ref.Kind != operatorsv1alpha1.ClusterServiceVersionKind {
   465  			continue
   466  		}
   467  		for _, c := range ref.Conditions {
   468  			if c.Reason == string(operatorsv1alpha1.CSVReasonCopied) {
   469  				return true, nil
   470  			}
   471  		}
   472  	}
   473  	return false, nil
   474  }
   475  
   476  func (matcher *copiedCSVRefMatcher) FailureMessage(actual interface{}) (message string) {
   477  	operator, ok := actual.(*operatorsv1.Operator)
   478  	if !ok {
   479  		return "copiedCSVRefMatcher matcher expects an *Operator"
   480  	}
   481  	return fmt.Sprintf("Expected\n\t%#v\nto contain copied CSVs in components\n\t%#v\n", operator, operator.Status.Components)
   482  }
   483  
   484  func (matcher *copiedCSVRefMatcher) NegatedFailureMessage(actual interface{}) (message string) {
   485  	operator, ok := actual.(*operatorsv1.Operator)
   486  	if !ok {
   487  		return "copiedCSVRefMatcher matcher expects an *Operator"
   488  	}
   489  	return fmt.Sprintf("Expected\n\t%#v\nto not contain copied CSVs in components\n\t%#v\n", operator, operator.Status.Components)
   490  }
   491  
   492  func operatorPredicate(fn func(*operatorsv1.Operator) bool) predicateFunc {
   493  	return func(event watch.Event) bool {
   494  		o, ok := event.Object.(*operatorsv1.Operator)
   495  		if !ok {
   496  			panic(fmt.Sprintf("unexpected event object type %T in deployment", event.Object))
   497  		}
   498  
   499  		return fn(o)
   500  	}
   501  }
   502  
   503  type OperatorMatcher struct {
   504  	matches func(*operatorsv1.Operator) (bool, error)
   505  	name    string
   506  }
   507  
   508  func (o OperatorMatcher) Match(actual interface{}) (bool, error) {
   509  	operator, ok := actual.(*operatorsv1.Operator)
   510  	if !ok {
   511  		return false, fmt.Errorf("OperatorMatcher expects Operator (got %T)", actual)
   512  	}
   513  
   514  	return o.matches(operator)
   515  }
   516  
   517  func (o OperatorMatcher) String() string {
   518  	return o.name
   519  }
   520  
   521  func (o OperatorMatcher) FailureMessage(actual interface{}) string {
   522  	return format.Message(actual, "to satisfy", o)
   523  }
   524  
   525  func (o OperatorMatcher) NegatedFailureMessage(actual interface{}) string {
   526  	return format.Message(actual, "not to satisfy", o)
   527  }
   528  
   529  func ReferenceComponents(refs []*corev1.ObjectReference) gomegatypes.GomegaMatcher {
   530  	return &OperatorMatcher{
   531  		matches: func(operator *operatorsv1.Operator) (bool, error) {
   532  			actual := map[corev1.ObjectReference]struct{}{}
   533  			for _, ref := range operator.Status.Components.Refs {
   534  				actual[*ref.ObjectReference] = struct{}{}
   535  			}
   536  
   537  			for _, ref := range refs {
   538  				if _, ok := actual[*ref]; !ok {
   539  					return false, nil
   540  				}
   541  			}
   542  
   543  			return true, nil
   544  		},
   545  		name: fmt.Sprintf("ReferenceComponents(%v)", refs),
   546  	}
   547  }