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