github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/operators/operator_controller_test.go (about)

     1  package operators
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	. "github.com/onsi/ginkgo/v2"
     8  	. "github.com/onsi/gomega"
     9  	corev1 "k8s.io/api/core/v1"
    10  	rbacv1 "k8s.io/api/rbac/v1"
    11  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    12  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    13  	"k8s.io/apimachinery/pkg/runtime"
    14  	"k8s.io/apimachinery/pkg/types"
    15  	"sigs.k8s.io/controller-runtime/pkg/client"
    16  
    17  	operatorsv1 "github.com/operator-framework/api/pkg/operators/v1"
    18  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/decorators"
    19  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/testobj"
    20  )
    21  
    22  var _ = Describe("Operator Controller", func() {
    23  	var (
    24  		ctx                            context.Context
    25  		operator                       *operatorsv1.Operator
    26  		name                           types.NamespacedName
    27  		expectedKey                    string
    28  		expectedComponentLabelSelector *metav1.LabelSelector
    29  	)
    30  
    31  	BeforeEach(func() {
    32  		ctx = context.Background()
    33  		operator = newOperator(genName("ghost-")).Operator
    34  		name = types.NamespacedName{Name: operator.GetName()}
    35  		expectedKey = decorators.ComponentLabelKeyPrefix + operator.GetName()
    36  		expectedComponentLabelSelector = &metav1.LabelSelector{
    37  			MatchExpressions: []metav1.LabelSelectorRequirement{
    38  				{
    39  					Key:      expectedKey,
    40  					Operator: metav1.LabelSelectorOpExists,
    41  				},
    42  			},
    43  		}
    44  
    45  		Expect(k8sClient.Create(ctx, operator)).To(Succeed())
    46  	})
    47  
    48  	Describe("operator deletion", func() {
    49  		var originalUID types.UID
    50  
    51  		JustBeforeEach(func() {
    52  			originalUID = operator.GetUID()
    53  			Expect(k8sClient.Delete(ctx, operator)).To(Succeed())
    54  		})
    55  
    56  		Context("with components bearing its label", func() {
    57  			var (
    58  				objs      []runtime.Object
    59  				namespace string
    60  			)
    61  
    62  			BeforeEach(func() {
    63  				namespace = genName("ns-")
    64  				objs = testobj.WithLabel(expectedKey, "",
    65  					testobj.WithName(namespace, &corev1.Namespace{}),
    66  				)
    67  
    68  				for _, obj := range objs {
    69  					Expect(k8sClient.Create(ctx, obj.(client.Object))).To(Succeed())
    70  				}
    71  			})
    72  
    73  			AfterEach(func() {
    74  				for _, obj := range objs {
    75  					Expect(k8sClient.Delete(ctx, obj.(client.Object), deleteOpts)).To(Succeed())
    76  				}
    77  				Expect(k8sClient.Delete(ctx, operator, deleteOpts)).To(Succeed())
    78  			})
    79  
    80  			It("should re-create it", func() {
    81  				// There's a race condition between this test and the controller. By the time,
    82  				// this function is running, we may be in one of three states.
    83  				//   1. The original deletion in the test setup has not yet finished, so the original
    84  				//      operator resource still exists.
    85  				//   2. The operator doesn't exist, and the controller has not yet re-created it.
    86  				//   3. The operator has already been deleted and re-created.
    87  				//
    88  				// To solve this problem, we simply compare the UIDs and expect to eventually see a
    89  				// a different UID.
    90  				Eventually(func() (types.UID, error) {
    91  					err := k8sClient.Get(ctx, name, operator)
    92  					return operator.GetUID(), err
    93  				}, timeout, interval).ShouldNot(Equal(originalUID))
    94  			})
    95  		})
    96  		Context("with no components bearing its label", func() {
    97  			It("should not re-create it", func() {
    98  				// We expect the operator deletion to eventually complete, and then we
    99  				// expect the operator to consistently never be found.
   100  				Eventually(func() bool {
   101  					return apierrors.IsNotFound(k8sClient.Get(ctx, name, operator))
   102  				}, timeout, interval).Should(BeTrue())
   103  				Consistently(func() bool {
   104  					return apierrors.IsNotFound(k8sClient.Get(ctx, name, operator))
   105  				}, timeout, interval).Should(BeTrue())
   106  			})
   107  		})
   108  	})
   109  
   110  	Describe("component selection", func() {
   111  
   112  		BeforeEach(func() {
   113  			Eventually(func() (*operatorsv1.Components, error) {
   114  				err := k8sClient.Get(ctx, name, operator)
   115  				return operator.Status.Components, err
   116  			}, timeout, interval).ShouldNot(BeNil())
   117  
   118  			Eventually(func() (*metav1.LabelSelector, error) {
   119  				err := k8sClient.Get(ctx, name, operator)
   120  				return operator.Status.Components.LabelSelector, err
   121  			}, timeout, interval).Should(Equal(expectedComponentLabelSelector))
   122  		})
   123  
   124  		AfterEach(func() {
   125  			Expect(k8sClient.Delete(ctx, operator, deleteOpts)).To(Succeed())
   126  		})
   127  
   128  		Context("with no components bearing its label", func() {
   129  			Specify("a status containing no component references", func() {
   130  				Consistently(func() ([]operatorsv1.RichReference, error) {
   131  					err := k8sClient.Get(ctx, name, operator)
   132  					return operator.Status.Components.Refs, err
   133  				}, timeout, interval).Should(BeEmpty())
   134  			})
   135  		})
   136  
   137  		Context("with components bearing its label", func() {
   138  			var (
   139  				objs         []runtime.Object
   140  				expectedRefs []operatorsv1.RichReference
   141  				namespace    string
   142  			)
   143  
   144  			BeforeEach(func() {
   145  				namespace = genName("ns-")
   146  				objs = testobj.WithLabel(expectedKey, "",
   147  					testobj.WithName(namespace, &corev1.Namespace{}),
   148  				)
   149  
   150  				for _, obj := range objs {
   151  					Expect(k8sClient.Create(ctx, obj.(client.Object))).To(Succeed())
   152  				}
   153  
   154  				expectedRefs = toRefs(scheme, objs...)
   155  			})
   156  
   157  			AfterEach(func() {
   158  				for _, obj := range objs {
   159  					Expect(k8sClient.Delete(ctx, obj.(client.Object), deleteOpts)).To(Succeed())
   160  				}
   161  			})
   162  
   163  			Specify("a status containing its component references", func() {
   164  				Eventually(func() ([]operatorsv1.RichReference, error) {
   165  					err := k8sClient.Get(ctx, name, operator)
   166  					return operator.Status.Components.Refs, err
   167  				}, timeout, interval).Should(ConsistOf(expectedRefs))
   168  			})
   169  
   170  			Context("when new components are labelled", func() {
   171  
   172  				BeforeEach(func() {
   173  					saName := &types.NamespacedName{Namespace: namespace, Name: genName("sa-")}
   174  					newObjs := testobj.WithLabel(expectedKey, "",
   175  						testobj.WithNamespacedName(saName, &corev1.ServiceAccount{}),
   176  						testobj.WithName(genName("sa-admin-"), &rbacv1.ClusterRoleBinding{
   177  							Subjects: []rbacv1.Subject{
   178  								{
   179  									Kind:      rbacv1.ServiceAccountKind,
   180  									Name:      saName.Name,
   181  									Namespace: saName.Namespace,
   182  								},
   183  							},
   184  							RoleRef: rbacv1.RoleRef{
   185  								APIGroup: rbacv1.GroupName,
   186  								Kind:     "ClusterRole",
   187  								Name:     "cluster-admin",
   188  							},
   189  						}),
   190  					)
   191  
   192  					for _, obj := range newObjs {
   193  						Expect(k8sClient.Create(ctx, obj.(client.Object))).To(Succeed())
   194  					}
   195  
   196  					objs = append(objs, newObjs...)
   197  					expectedRefs = append(expectedRefs, toRefs(scheme, newObjs...)...)
   198  				})
   199  
   200  				It("should add the component references", func() {
   201  					Eventually(func() ([]operatorsv1.RichReference, error) {
   202  						err := k8sClient.Get(ctx, name, operator)
   203  						return operator.Status.Components.Refs, err
   204  					}, timeout, interval).Should(ConsistOf(expectedRefs))
   205  				})
   206  			})
   207  
   208  			Context("when multiple types of a gvk are labeled", func() {
   209  				BeforeEach(func() {
   210  					newObjs := make([]runtime.Object, 9)
   211  
   212  					// Create objects in reverse order to ensure they are eventually ordered alphabetically in the status of the operator.
   213  					for i := 8; i >= 0; i-- {
   214  						newObjs[8-i] = testobj.WithLabels(map[string]string{expectedKey: ""}, testobj.WithNamespacedName(
   215  							&types.NamespacedName{Namespace: namespace, Name: fmt.Sprintf("sa-%d", i)}, &corev1.ServiceAccount{},
   216  						))
   217  					}
   218  
   219  					for _, obj := range newObjs {
   220  						Expect(k8sClient.Create(ctx, obj.(client.Object))).To(Succeed())
   221  					}
   222  
   223  					objs = append(objs, newObjs...)
   224  					expectedRefs = append(expectedRefs, toRefs(scheme, newObjs...)...)
   225  				})
   226  
   227  				It("should list each of the component references in alphabetical order by namespace and name", func() {
   228  					Eventually(func() ([]operatorsv1.RichReference, error) {
   229  						err := k8sClient.Get(ctx, name, operator)
   230  						return operator.Status.Components.Refs, err
   231  					}, timeout, interval).Should(ConsistOf(expectedRefs))
   232  
   233  					serviceAccountCount := 0
   234  					for _, ref := range operator.Status.Components.Refs {
   235  						if ref.Kind != rbacv1.ServiceAccountKind {
   236  							continue
   237  						}
   238  
   239  						Expect(ref.Name).Should(Equal(fmt.Sprintf("sa-%d", serviceAccountCount)))
   240  						serviceAccountCount++
   241  					}
   242  					Expect(serviceAccountCount).To(Equal(9))
   243  				})
   244  			})
   245  
   246  			Context("when component labels are removed", func() {
   247  
   248  				BeforeEach(func() {
   249  					for _, obj := range testobj.StripLabel(expectedKey, objs...) {
   250  						Expect(k8sClient.Update(ctx, obj.(client.Object))).To(Succeed())
   251  					}
   252  				})
   253  
   254  				It("should remove the component references", func() {
   255  					Eventually(func() ([]operatorsv1.RichReference, error) {
   256  						err := k8sClient.Get(ctx, name, operator)
   257  						return operator.Status.Components.Refs, err
   258  					}, timeout, interval).Should(BeEmpty())
   259  				})
   260  			})
   261  		})
   262  	})
   263  })