@@ -61,12 +61,10 @@ func TestTLSProfileWatcher(t *testing.T) {
6161// running and healthy when the cluster uses the default TLS profile (no
6262// tlsSecurityProfile set on the APIServer CR, which defaults to Intermediate).
6363func assertOperatorRunningWithDefaultTLSProfile (t * testing.T ) {
64- // Verify the APIServer CR exists and is readable.
6564 apiServer := & configv1.APIServer {}
6665 err := f .K8sClient .Get (context .Background (), types.NamespacedName {Name : "cluster" }, apiServer )
6766 assert .NilError (t , err , "failed to get APIServer CR" )
6867
69- // Verify the operator deployment is ready.
7068 f .AssertDeploymentReady (operatorDeploymentName , f .OperatorNamespace ,
7169 framework .WithTimeout (2 * time .Minute ))(t )
7270
@@ -78,36 +76,29 @@ func assertOperatorRunningWithDefaultTLSProfile(t *testing.T) {
7876func assertOperatorRestartsOnTLSProfileChange (t * testing.T ) {
7977 ctx := context .Background ()
8078
81- // Save original APIServer spec so we can restore it.
8279 apiServer := & configv1.APIServer {}
8380 err := f .K8sClient .Get (ctx , types.NamespacedName {Name : "cluster" }, apiServer )
8481 assert .NilError (t , err , "failed to get APIServer CR" )
8582 originalTLSProfile := apiServer .Spec .TLSSecurityProfile
8683
87- // Ensure we restore the original profile even if the test fails.
8884 f .CleanUp (t , func () {
8985 restoreTLSProfile (t , originalTLSProfile )
9086 })
9187
92- // Wait for the operator to be stable before we start.
9388 f .AssertDeploymentReady (operatorDeploymentName , f .OperatorNamespace ,
9489 framework .WithTimeout (2 * time .Minute ))(t )
9590
96- // Record the current container restart count.
9791 initialRestarts := getOperatorContainerRestartCount (t )
9892 t .Logf ("container restart count before TLS change: %d" , initialRestarts )
9993
100- // Change TLS profile to Old.
10194 t .Log ("setting TLS profile to Old" )
10295 setTLSProfile (t , & configv1.TLSSecurityProfile {
10396 Type : configv1 .TLSProfileOldType ,
10497 Old : & configv1.OldTLSProfile {},
10598 })
10699
107- // The operator should restart: wait for the restart count to increase.
108100 waitForOperatorContainerRestart (t , initialRestarts )
109101
110- // After restart, the operator should become ready again.
111102 f .AssertDeploymentReady (operatorDeploymentName , f .OperatorNamespace ,
112103 framework .WithTimeout (5 * time .Minute ))(t )
113104 t .Log ("operator restarted and is ready after TLS profile change to Old" )
@@ -131,12 +122,8 @@ func assertOperatorRestartsOnCustomTLSProfile(t *testing.T) {
131122 f .AssertDeploymentReady (operatorDeploymentName , f .OperatorNamespace ,
132123 framework .WithTimeout (5 * time .Minute ))(t )
133124
134- // Wait for the restart count to stabilize — the previous test's cleanup
135- // may have triggered a restart that hasn't completed yet.
136125 initialRestarts := waitForStableRestartCount (t )
137126
138- // Set a Custom TLS profile that differs from the default Intermediate
139- // profile by using a smaller cipher list (a subset of Intermediate's ciphers).
140127 t .Log ("setting TLS profile to Custom" )
141128 setTLSProfile (t , & configv1.TLSSecurityProfile {
142129 Type : configv1 .TLSProfileCustomType ,
@@ -173,7 +160,6 @@ func assertOperatorStableOnNonTLSChange(t *testing.T) {
173160 framework .WithTimeout (5 * time .Minute ))(t )
174161 initialRestarts := waitForStableRestartCount (t )
175162
176- // Update a non-TLS field: add a test annotation.
177163 const testAnnotation = "observability-operator.rhobs/e2e-tls-test"
178164 apiServer := & configv1.APIServer {}
179165 err := f .K8sClient .Get (ctx , types.NamespacedName {Name : "cluster" }, apiServer )
@@ -201,22 +187,36 @@ func assertOperatorStableOnNonTLSChange(t *testing.T) {
201187 assert .NilError (t , err , "failed to patch APIServer with test annotation" )
202188 t .Log ("added test annotation to APIServer CR" )
203189
204- // Wait a reasonable period and verify the restart count has not increased.
190+ originalPod := getRunningOperatorPod (t )
191+ originalPodUID := originalPod .UID
192+
205193 t .Log ("waiting 60s to confirm operator does not restart" )
194+ var lastErr error
206195 err = wait .PollUntilContextTimeout (ctx , 10 * time .Second , 60 * time .Second , true , func (ctx context.Context ) (bool , error ) {
207- currentRestarts := getOperatorContainerRestartCount (t )
208- if currentRestarts > initialRestarts {
209- return true , fmt .Errorf ("operator restarted unexpectedly: restart count changed from %d to %d" , initialRestarts , currentRestarts )
196+ pod , podErr := runningOperatorPod ()
197+ if podErr != nil {
198+ lastErr = podErr
199+ return false , nil
200+ }
201+ lastErr = nil
202+ if pod .UID != originalPodUID {
203+ return true , fmt .Errorf ("operator pod replaced unexpectedly: old UID=%s, new UID=%s" , originalPodUID , pod .UID )
210204 }
211- return false , nil // keep polling — we want this to NOT match (timeout = success)
205+ for _ , cs := range pod .Status .ContainerStatuses {
206+ if cs .Name == operatorContainerName && cs .RestartCount > initialRestarts {
207+ return true , fmt .Errorf ("operator restarted unexpectedly: restart count changed from %d to %d" , initialRestarts , cs .RestartCount )
208+ }
209+ }
210+ return false , nil
212211 })
213212
214213 if wait .Interrupted (err ) {
215- // Timeout means the restart count never changed — that's what we want.
214+ if lastErr != nil {
215+ t .Fatalf ("operator pod was not observable during stability window: %v" , lastErr )
216+ }
216217 t .Log ("operator remained stable after non-TLS APIServer change" )
217218 return
218219 }
219- // If we get here, it means the restart count increased — fail.
220220 assert .NilError (t , err , "operator should not restart on non-TLS APIServer changes" )
221221}
222222
@@ -254,8 +254,7 @@ func restoreTLSProfile(t *testing.T, profile *configv1.TLSSecurityProfile) {
254254 return
255255 }
256256
257- // Wait for operator to recover after the restore.
258- _ = wait .PollUntilContextTimeout (ctx , 5 * time .Second , 5 * time .Minute , true , func (ctx context.Context ) (bool , error ) {
257+ if err := wait .PollUntilContextTimeout (ctx , 5 * time .Second , 5 * time .Minute , true , func (ctx context.Context ) (bool , error ) {
259258 dep := & appsv1.Deployment {}
260259 if err := f .K8sClient .Get (ctx , types.NamespacedName {
261260 Name : operatorDeploymentName ,
@@ -264,55 +263,82 @@ func restoreTLSProfile(t *testing.T, profile *configv1.TLSSecurityProfile) {
264263 return false , nil
265264 }
266265 return dep .Status .ReadyReplicas == * dep .Spec .Replicas , nil
267- })
266+ }); err != nil {
267+ t .Logf ("cleanup: operator deployment did not become ready: %v" , err )
268+ }
268269}
269270
270- // getOperatorContainerRestartCount returns the total restart count of the
271- // operator container in the running operator pod.
272- func getOperatorContainerRestartCount (t * testing.T ) int32 {
273- t .Helper ()
274-
275- pod := getRunningOperatorPod (t )
271+ // operatorContainerRestartCount returns the restart count for the operator
272+ // container, or an error if the pod or container cannot be found. This is
273+ // safe to call inside poll loops since it does not call t.Fatal.
274+ func operatorContainerRestartCount () (int32 , error ) {
275+ pod , err := runningOperatorPod ()
276+ if err != nil {
277+ return 0 , err
278+ }
276279 for _ , cs := range pod .Status .ContainerStatuses {
277280 if cs .Name == operatorContainerName {
278- return cs .RestartCount
281+ return cs .RestartCount , nil
279282 }
280283 }
281-
282- t .Fatalf ("container %q not found in operator pod" , operatorContainerName )
283- return 0
284+ return 0 , fmt .Errorf ("container %q not found in operator pod" , operatorContainerName )
284285}
285286
286- // getRunningOperatorPod returns the running operator pod.
287- func getRunningOperatorPod (t * testing.T ) * corev1.Pod {
288- t .Helper ()
287+ // runningOperatorPod returns the running operator pod, or an error if it
288+ // cannot be found. This is safe to call inside poll loops since it does
289+ // not call t.Fatal.
290+ func runningOperatorPod () (* corev1.Pod , error ) {
291+ ctx := context .Background ()
289292
290293 dep := & appsv1.Deployment {}
291- err := f .K8sClient .Get (context . Background () , types.NamespacedName {
294+ if err := f .K8sClient .Get (ctx , types.NamespacedName {
292295 Name : operatorDeploymentName ,
293296 Namespace : f .OperatorNamespace ,
294- }, dep )
295- assert .NilError (t , err , "failed to get operator deployment" )
297+ }, dep ); err != nil {
298+ return nil , fmt .Errorf ("failed to get operator deployment: %w" , err )
299+ }
296300
297301 selector , err := metav1 .LabelSelectorAsSelector (dep .Spec .Selector )
298- assert .NilError (t , err , "failed to parse deployment selector" )
302+ if err != nil {
303+ return nil , fmt .Errorf ("failed to parse deployment selector: %w" , err )
304+ }
299305
300306 var pods corev1.PodList
301- err = f .K8sClient .List (context . Background () , & pods ,
307+ if err : = f .K8sClient .List (ctx , & pods ,
302308 client .InNamespace (f .OperatorNamespace ),
303309 client.MatchingLabelsSelector {Selector : selector },
304- )
305- assert .NilError (t , err , "failed to list operator pods" )
310+ ); err != nil {
311+ return nil , fmt .Errorf ("failed to list operator pods: %w" , err )
312+ }
306313
307314 for i := range pods .Items {
308315 p := & pods .Items [i ]
309316 if p .Status .Phase == corev1 .PodRunning && p .DeletionTimestamp == nil {
310- return p
317+ return p , nil
311318 }
312319 }
313320
314- t .Fatal ("no running operator pod found" )
315- return nil
321+ return nil , fmt .Errorf ("no running operator pod found" )
322+ }
323+
324+ // getOperatorContainerRestartCount returns the restart count for the operator
325+ // container. It calls t.Fatal if the pod or container cannot be found; use
326+ // operatorContainerRestartCount() instead when calling from inside poll loops.
327+ func getOperatorContainerRestartCount (t * testing.T ) int32 {
328+ t .Helper ()
329+ count , err := operatorContainerRestartCount ()
330+ assert .NilError (t , err )
331+ return count
332+ }
333+
334+ // getRunningOperatorPod returns the running operator pod. It calls t.Fatal if
335+ // the pod cannot be found; use runningOperatorPod() instead when calling from
336+ // inside poll loops.
337+ func getRunningOperatorPod (t * testing.T ) * corev1.Pod {
338+ t .Helper ()
339+ pod , err := runningOperatorPod ()
340+ assert .NilError (t , err )
341+ return pod
316342}
317343
318344// waitForOperatorContainerRestart polls until the operator process has
@@ -322,7 +348,6 @@ func getRunningOperatorPod(t *testing.T) *corev1.Pod {
322348func waitForOperatorContainerRestart (t * testing.T , baselineRestarts int32 ) {
323349 t .Helper ()
324350
325- // Record the current pod UID so we can detect pod replacement.
326351 originalPod := getRunningOperatorPod (t )
327352 originalPodUID := originalPod .UID
328353
@@ -352,14 +377,12 @@ func waitForOperatorContainerRestart(t *testing.T, baselineRestarts int32) {
352377 if p .DeletionTimestamp != nil {
353378 continue
354379 }
355- // Detect pod replacement (new pod UID).
356380 if p .UID != originalPodUID && p .Status .Phase == corev1 .PodRunning {
357381 t .Logf ("operator pod replaced: old UID=%s, new UID=%s" , originalPodUID , p .UID )
358382 return true , nil
359383 }
360- // Detect container restart within the same pod.
361384 for _ , cs := range p .Status .ContainerStatuses {
362- if cs .RestartCount > baselineRestarts {
385+ if cs .Name == operatorContainerName && cs . RestartCount > baselineRestarts {
363386 t .Logf ("operator container restarted: restart count %d -> %d" , baselineRestarts , cs .RestartCount )
364387 return true , nil
365388 }
@@ -381,7 +404,10 @@ func waitForStableRestartCount(t *testing.T) int32 {
381404 var stableSince time.Time
382405
383406 err := wait .PollUntilContextTimeout (context .Background (), 5 * time .Second , 5 * time .Minute , true , func (ctx context.Context ) (bool , error ) {
384- current := getOperatorContainerRestartCount (t )
407+ current , restartErr := operatorContainerRestartCount ()
408+ if restartErr != nil {
409+ return false , nil
410+ }
385411 if current != lastCount {
386412 lastCount = current
387413 stableSince = time .Now ()
@@ -391,20 +417,26 @@ func waitForStableRestartCount(t *testing.T) int32 {
391417 return false , nil
392418 }
393419
394- // Also verify the container has been running long enough for
395- // the watcher to process its initial reconcile event.
396- pod := getRunningOperatorPod (t )
420+ pod , podErr := runningOperatorPod ()
421+ if podErr != nil {
422+ return false , nil
423+ }
397424 for _ , cs := range pod .Status .ContainerStatuses {
398- if cs .State .Running != nil {
399- uptime := time .Since (cs .State .Running .StartedAt .Time )
400- if uptime < 15 * time .Second {
401- t .Logf ("operator container uptime %s < 15s, waiting longer" , uptime .Round (time .Second ))
402- return false , nil
403- }
425+ if cs .Name != operatorContainerName {
426+ continue
427+ }
428+ if cs .State .Running == nil {
429+ return false , nil
404430 }
431+ uptime := time .Since (cs .State .Running .StartedAt .Time )
432+ if uptime < 15 * time .Second {
433+ t .Logf ("operator container uptime %s < 15s, waiting longer" , uptime .Round (time .Second ))
434+ return false , nil
435+ }
436+ return true , nil
405437 }
406438
407- return true , nil
439+ return false , nil
408440 })
409441 assert .NilError (t , err , "operator restart count did not stabilize within timeout" )
410442 t .Logf ("operator restart count stabilized at %d" , lastCount )
0 commit comments