@@ -19,19 +19,27 @@ package controllers
1919import (
2020 "context"
2121 "fmt"
22+ "sync"
2223 "testing"
2324 "time"
2425
2526 cmutil "github.com/cert-manager/cert-manager/pkg/api/util"
2627 cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
2728 cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
2829 cmgen "github.com/cert-manager/cert-manager/test/unit/gen"
30+ "github.com/stretchr/testify/assert"
2931 "github.com/stretchr/testify/require"
3032 "k8s.io/apimachinery/pkg/runtime"
33+ "k8s.io/apimachinery/pkg/runtime/schema"
3134 "k8s.io/apimachinery/pkg/watch"
3235 "k8s.io/client-go/tools/record"
36+ "k8s.io/client-go/util/retry"
37+ "k8s.io/client-go/util/workqueue"
3338 "k8s.io/utils/clock"
3439 ctrl "sigs.k8s.io/controller-runtime"
40+ "sigs.k8s.io/controller-runtime/pkg/builder"
41+ "sigs.k8s.io/controller-runtime/pkg/client"
42+ "sigs.k8s.io/controller-runtime/pkg/controller"
3543
3644 v1alpha1 "github.com/cert-manager/issuer-lib/api/v1alpha1"
3745 "github.com/cert-manager/issuer-lib/conditions"
@@ -278,3 +286,290 @@ func TestCombinedControllerTemporaryFailedCertificateRequestRetrigger(t *testing
278286 })
279287 }
280288}
289+
290+ func TestCombinedControllerTiming (t * testing.T ) { //nolint:tparallel
291+ t .Parallel ()
292+
293+ t .Log (
294+ "Tests to show that the CertificateRequest controller and Issuer controller call the Check and Sign functions at the correct times" ,
295+ )
296+
297+ fieldOwner := "failed-certificate-request-should-retrigger-issuer"
298+
299+ ctx := testcontext .ForTest (t )
300+ kubeClients := testresource .KubeClients (t , nil )
301+
302+ type simulatedCheckResult struct {
303+ err error
304+ }
305+ type simulatedSignResult struct {
306+ cert []byte
307+ err error
308+ }
309+
310+ type simulatedResult struct {
311+ * simulatedCheckResult
312+ * simulatedSignResult
313+ expectedSinceLastResult time.Duration
314+ }
315+
316+ type testcase struct {
317+ name string
318+ maxRetryDuration time.Duration
319+ results []simulatedResult
320+ }
321+
322+ testcases := []testcase {
323+ {
324+ name : "single-error-for-issuer-and-certificate-request" ,
325+ maxRetryDuration : 1 * time .Hour ,
326+ results : []simulatedResult {
327+ {
328+ simulatedCheckResult : & simulatedCheckResult {err : fmt .Errorf ("[error message]" )},
329+ expectedSinceLastResult : 0 ,
330+ },
331+ {
332+ simulatedCheckResult : & simulatedCheckResult {err : nil },
333+ expectedSinceLastResult : 200 * time .Millisecond ,
334+ },
335+ {
336+ simulatedSignResult : & simulatedSignResult {cert : nil , err : fmt .Errorf ("[error message]" )},
337+ expectedSinceLastResult : 0 ,
338+ },
339+ {
340+ simulatedSignResult : & simulatedSignResult {cert : []byte ("cert" ), err : nil },
341+ expectedSinceLastResult : 200 * time .Millisecond ,
342+ },
343+ },
344+ },
345+ {
346+ name : "double-error-for-issuer-and-certificate-request" ,
347+ maxRetryDuration : 1 * time .Hour ,
348+ results : []simulatedResult {
349+ {
350+ simulatedCheckResult : & simulatedCheckResult {err : fmt .Errorf ("[error message]" )},
351+ expectedSinceLastResult : 0 ,
352+ },
353+ {
354+ simulatedCheckResult : & simulatedCheckResult {err : fmt .Errorf ("[error message]" )},
355+ expectedSinceLastResult : 200 * time .Millisecond ,
356+ },
357+ {
358+ simulatedCheckResult : & simulatedCheckResult {err : nil },
359+ expectedSinceLastResult : 400 * time .Millisecond ,
360+ },
361+ {
362+ simulatedSignResult : & simulatedSignResult {cert : nil , err : fmt .Errorf ("[error message]" )},
363+ expectedSinceLastResult : 0 ,
364+ },
365+ {
366+ simulatedSignResult : & simulatedSignResult {cert : nil , err : fmt .Errorf ("[error message]" )},
367+ expectedSinceLastResult : 200 * time .Millisecond ,
368+ },
369+ {
370+ simulatedSignResult : & simulatedSignResult {cert : []byte ("cert" ), err : nil },
371+ expectedSinceLastResult : 400 * time .Millisecond ,
372+ },
373+ },
374+ },
375+ {
376+ name : "single-error-for-issuer-and-certificate-request-reaching-max-retry-duration" ,
377+ maxRetryDuration : 300 * time .Millisecond , // should cause temporary CertificateRequest errors to fail permanently
378+ results : []simulatedResult {
379+ {
380+ simulatedCheckResult : & simulatedCheckResult {err : fmt .Errorf ("[error message]" )},
381+ expectedSinceLastResult : 0 ,
382+ },
383+ {
384+ simulatedCheckResult : & simulatedCheckResult {err : nil },
385+ expectedSinceLastResult : 200 * time .Millisecond ,
386+ },
387+ {
388+ simulatedSignResult : & simulatedSignResult {cert : nil , err : fmt .Errorf ("[error message]" )},
389+ expectedSinceLastResult : 0 ,
390+ },
391+ },
392+ },
393+ {
394+ name : "single-pending-error-for-issuer-and-certificate-request-reaching-max-retry-duration" ,
395+ maxRetryDuration : 300 * time .Millisecond , // should cause temporary CertificateRequest errors to fail permanently
396+ results : []simulatedResult {
397+ {
398+ simulatedCheckResult : & simulatedCheckResult {err : fmt .Errorf ("[error message]" )},
399+ expectedSinceLastResult : 0 ,
400+ },
401+ {
402+ simulatedCheckResult : & simulatedCheckResult {err : nil },
403+ expectedSinceLastResult : 200 * time .Millisecond ,
404+ },
405+ {
406+ simulatedSignResult : & simulatedSignResult {cert : nil , err : signer.PendingError {Err : fmt .Errorf ("[error message]" )}},
407+ expectedSinceLastResult : 0 ,
408+ },
409+ {
410+ simulatedSignResult : & simulatedSignResult {cert : []byte ("ok" ), err : nil },
411+ expectedSinceLastResult : 200 * time .Millisecond ,
412+ },
413+ },
414+ },
415+ {
416+ name : "fail-issuer-permanently" ,
417+ maxRetryDuration : 1 * time .Hour ,
418+ results : []simulatedResult {
419+ {
420+ simulatedCheckResult : & simulatedCheckResult {err : signer.PermanentError {Err : fmt .Errorf ("[error message]" )}},
421+ expectedSinceLastResult : 0 ,
422+ },
423+ },
424+ },
425+ {
426+ name : "trigger-issuer-error-then-recover" ,
427+ maxRetryDuration : 1 * time .Hour ,
428+ results : []simulatedResult {
429+ {
430+ simulatedCheckResult : & simulatedCheckResult {err : nil },
431+ expectedSinceLastResult : 0 ,
432+ },
433+ {
434+ simulatedSignResult : & simulatedSignResult {cert : nil , err : signer.IssuerError {Err : fmt .Errorf ("[error message]" )}},
435+ expectedSinceLastResult : 0 ,
436+ },
437+ {
438+ simulatedCheckResult : & simulatedCheckResult {err : nil },
439+ expectedSinceLastResult : 200 * time .Millisecond ,
440+ },
441+ {
442+ simulatedSignResult : & simulatedSignResult {cert : []byte ("ok" ), err : nil },
443+ expectedSinceLastResult : 0 ,
444+ },
445+ },
446+ },
447+ }
448+
449+ for _ , tc := range testcases {
450+ tc := tc
451+
452+ t .Run (tc .name , func (t * testing.T ) {
453+ resultsMutex := sync.Mutex {}
454+ resultsIndex := 0
455+ results := tc .results
456+ durations := make ([]time.Time , len (results ))
457+ errorCh := make (chan error )
458+ done := make (chan struct {})
459+
460+ ctx := setupControllersAPIServerAndClient (t , ctx , kubeClients ,
461+ func (mgr ctrl.Manager ) controllerInterface {
462+ return & CombinedController {
463+ IssuerTypes : []v1alpha1.Issuer {& api.TestIssuer {}},
464+ ClusterIssuerTypes : []v1alpha1.Issuer {& api.TestClusterIssuer {}},
465+ FieldOwner : fieldOwner ,
466+ MaxRetryDuration : tc .maxRetryDuration ,
467+ Check : func (_ context.Context , _ v1alpha1.Issuer ) error {
468+ resultsMutex .Lock ()
469+ defer resultsMutex .Unlock ()
470+ defer func () { resultsIndex ++ }()
471+
472+ if resultsIndex >= len (results )- 1 {
473+ if resultsIndex > len (results )- 1 {
474+ errorCh <- fmt .Errorf ("too many calls to Check" )
475+ return nil
476+ }
477+ defer close (done )
478+ }
479+ durations [resultsIndex ] = time .Now ()
480+ if results [resultsIndex ].simulatedCheckResult == nil {
481+ errorCh <- fmt .Errorf ("unexpected call to Check" )
482+ return nil
483+ }
484+ return results [resultsIndex ].simulatedCheckResult .err
485+ },
486+ Sign : func (_ context.Context , _ signer.CertificateRequestObject , _ v1alpha1.Issuer ) (signer.PEMBundle , error ) {
487+ resultsMutex .Lock ()
488+ defer resultsMutex .Unlock ()
489+ defer func () { resultsIndex ++ }()
490+
491+ if resultsIndex >= len (results )- 1 {
492+ if resultsIndex > len (results )- 1 {
493+ errorCh <- fmt .Errorf ("too many calls to Sign" )
494+ return signer.PEMBundle {}, nil
495+ }
496+ defer close (done )
497+ }
498+ durations [resultsIndex ] = time .Now ()
499+ if results [resultsIndex ].simulatedSignResult == nil {
500+ errorCh <- fmt .Errorf ("unexpected call to Sign" )
501+ return signer.PEMBundle {}, nil
502+ }
503+ result := results [resultsIndex ].simulatedSignResult
504+ return signer.PEMBundle {
505+ ChainPEM : result .cert ,
506+ }, result .err
507+ },
508+ EventRecorder : record .NewFakeRecorder (100 ),
509+
510+ PreSetupWithManager : func (ctx context.Context , gvk schema.GroupVersionKind , mgr ctrl.Manager , b * builder.Builder ) error {
511+ b .WithOptions (controller.Options {
512+ RateLimiter : workqueue .NewItemExponentialFailureRateLimiter (200 * time .Millisecond , 5 * time .Second ),
513+ })
514+ return nil
515+ },
516+ }
517+ },
518+ )
519+
520+ t .Logf ("Creating a namespace" )
521+ namespace , cleanup := kubeClients .SetupNamespace (t , ctx )
522+ defer cleanup ()
523+
524+ issuer := testutil .TestIssuer (
525+ "issuer-1" ,
526+ testutil .SetTestIssuerNamespace (namespace ),
527+ )
528+
529+ cr := cmgen .CertificateRequest (
530+ "certificate-request-1" ,
531+ cmgen .SetCertificateRequestNamespace (namespace ),
532+ cmgen .SetCertificateRequestCSR ([]byte ("doo" )),
533+ cmgen .SetCertificateRequestIssuer (cmmeta.ObjectReference {
534+ Name : issuer .Name ,
535+ Kind : issuer .Kind ,
536+ Group : api .SchemeGroupVersion .Group ,
537+ }),
538+ )
539+
540+ require .NoError (t , kubeClients .Client .Create (ctx , issuer ))
541+ createApprovedCR (t , ctx , kubeClients .Client , clock.RealClock {}, cr )
542+
543+ <- done
544+ time .Sleep (1 * time .Second )
545+ select {
546+ case err := <- errorCh :
547+ assert .NoError (t , err )
548+ default :
549+ }
550+
551+ require .NoError (t , retry .RetryOnConflict (retry .DefaultRetry , func () error {
552+ err := kubeClients .Client .Get (ctx , client .ObjectKeyFromObject (cr ), cr )
553+ if err != nil {
554+ return err
555+ }
556+ return kubeClients .Client .Delete (ctx , cr )
557+ }))
558+ require .NoError (t , retry .RetryOnConflict (retry .DefaultRetry , func () error {
559+ err := kubeClients .Client .Get (ctx , client .ObjectKeyFromObject (issuer ), issuer )
560+ if err != nil {
561+ return err
562+ }
563+ return kubeClients .Client .Delete (ctx , issuer )
564+ }))
565+
566+ for i := 1 ; i < len (results ); i ++ {
567+ measuredDuration := durations [i ].Sub (durations [i - 1 ])
568+ expectedDuration := results [i ].expectedSinceLastResult
569+
570+ require .True (t , expectedDuration - 150 * time .Millisecond < measuredDuration , "result %d: expected %v, got %v" , i , expectedDuration , measuredDuration )
571+ require .True (t , expectedDuration + 150 * time .Millisecond > measuredDuration , "result %d: expected %v, got %v" , i , expectedDuration , measuredDuration )
572+ }
573+ })
574+ }
575+ }
0 commit comments