@@ -24,18 +24,22 @@ import (
24
24
"io"
25
25
"net/http"
26
26
"strings"
27
+ "sync/atomic"
27
28
"testing"
29
+ "time"
28
30
29
31
"github.com/stretchr/testify/require"
30
32
"github.com/stretchr/testify/suite"
31
33
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
34
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
32
35
"k8s.io/apimachinery/pkg/labels"
33
36
"k8s.io/apimachinery/pkg/runtime"
34
37
"k8s.io/apimachinery/pkg/runtime/schema"
35
38
"k8s.io/apimachinery/pkg/runtime/serializer"
36
39
"k8s.io/client-go/rest"
37
40
"k8s.io/client-go/rest/fake"
38
-
41
+ "k8s.io/client-go/tools/cache"
42
+ cachetesting "k8s.io/client-go/tools/cache/testing"
39
43
apiv1alpha1 "sigs.k8s.io/external-dns/apis/v1alpha1"
40
44
"sigs.k8s.io/external-dns/endpoint"
41
45
)
@@ -83,7 +87,6 @@ func fakeRESTClient(endpoints []*endpoint.Endpoint, apiVersion, kind, namespace,
83
87
codecFactory := serializer.WithoutConversionCodecFactory {
84
88
CodecFactory : serializer .NewCodecFactory (scheme ),
85
89
}
86
-
87
90
client := & fake.RESTClient {
88
91
GroupVersion : groupVersion ,
89
92
VersionedAPIPath : "/apis/" + apiVersion ,
@@ -103,7 +106,10 @@ func fakeRESTClient(endpoints []*endpoint.Endpoint, apiVersion, kind, namespace,
103
106
decoder := json .NewDecoder (req .Body )
104
107
105
108
var body apiv1alpha1.DNSEndpoint
106
- decoder .Decode (& body )
109
+ err := decoder .Decode (& body )
110
+ if err != nil {
111
+ return nil , err
112
+ }
107
113
dnsEndpoint .Status .ObservedGeneration = body .Status .ObservedGeneration
108
114
return & http.Response {StatusCode : http .StatusOK , Header : defaultHeader (), Body : objBody (codec , dnsEndpoint )}, nil
109
115
default :
@@ -475,22 +481,22 @@ func testCRDSourceEndpoints(t *testing.T) {
475
481
restClient := fakeRESTClient (ti .endpoints , ti .registeredAPIVersion , ti .registeredKind , ti .registeredNamespace , "test" , ti .annotations , ti .labels , t )
476
482
groupVersion , err := schema .ParseGroupVersion (ti .apiVersion )
477
483
require .NoError (t , err )
484
+ require .NotNil (t , groupVersion )
478
485
479
486
scheme := runtime .NewScheme ()
480
- require .NoError (t , addKnownTypes (scheme , groupVersion ))
487
+ err = addKnownTypes (scheme , groupVersion )
488
+ require .NoError (t , err )
481
489
482
490
labelSelector , err := labels .Parse (ti .labelFilter )
483
491
require .NoError (t , err )
484
492
485
493
// At present, client-go's fake.RESTClient (used by crd_test.go) is known to cause race conditions when used
486
494
// with informers: https://github.com/kubernetes/kubernetes/issues/95372
487
495
// So don't start the informer during testing.
488
- startInformer := false
489
-
490
- cs , err := NewCRDSource (restClient , ti .namespace , ti .kind , ti .annotationFilter , labelSelector , scheme , startInformer )
496
+ cs , err := NewCRDSource (restClient , ti .namespace , ti .kind , ti .annotationFilter , labelSelector , scheme , false )
491
497
require .NoError (t , err )
492
498
493
- receivedEndpoints , err := cs .Endpoints (context . Background ())
499
+ receivedEndpoints , err := cs .Endpoints (t . Context ())
494
500
if ti .expectError {
495
501
require .Errorf (t , err , "Received err %v" , err )
496
502
} else {
@@ -511,7 +517,133 @@ func testCRDSourceEndpoints(t *testing.T) {
511
517
}
512
518
}
513
519
520
+ func TestCRDSource_NoInformer (t * testing.T ) {
521
+ cs := & crdSource {informer : nil }
522
+ called := false
523
+
524
+ cs .AddEventHandler (context .Background (), func () { called = true })
525
+ require .False (t , called , "handler must not be called when informer is nil" )
526
+ }
527
+
528
+ func TestCRDSource_AddEventHandler_Add (t * testing.T ) {
529
+ ctx := t .Context ()
530
+ watcher , cs := helperCreateWatcherWithInformer (t )
531
+
532
+ var counter atomic.Int32
533
+ cs .AddEventHandler (ctx , func () {
534
+ counter .Add (1 )
535
+ })
536
+
537
+ obj := & unstructured.Unstructured {}
538
+ obj .SetName ("test" )
539
+
540
+ watcher .Add (obj )
541
+
542
+ require .Eventually (t , func () bool {
543
+ return counter .Load () == 1
544
+ }, time .Second , 10 * time .Millisecond )
545
+ }
546
+
547
+ func TestCRDSource_AddEventHandler_Update (t * testing.T ) {
548
+ ctx := t .Context ()
549
+ watcher , cs := helperCreateWatcherWithInformer (t )
550
+
551
+ var counter atomic.Int32
552
+ cs .AddEventHandler (ctx , func () {
553
+ counter .Add (1 )
554
+ })
555
+
556
+ obj := unstructured.Unstructured {}
557
+ obj .SetName ("test" )
558
+ obj .SetNamespace ("default" )
559
+ obj .SetUID ("9be5b64e-3ee9-11f0-88ee-1eb95c6fd730" )
560
+
561
+ watcher .Add (& obj )
562
+
563
+ require .Eventually (t , func () bool {
564
+ return len (watcher .Items ) == 1
565
+ }, time .Second , 10 * time .Millisecond )
566
+
567
+ modified := obj .DeepCopy ()
568
+ modified .SetLabels (map [string ]string {"new-label" : "this" })
569
+ watcher .Modify (modified )
570
+
571
+ require .Eventually (t , func () bool {
572
+ return len (watcher .Items ) == 1
573
+ }, time .Second , 10 * time .Millisecond )
574
+
575
+ require .Eventually (t , func () bool {
576
+ return counter .Load () == 2
577
+ }, time .Second , 10 * time .Millisecond )
578
+ }
579
+
580
+ func TestCRDSource_AddEventHandler_Delete (t * testing.T ) {
581
+ ctx := t .Context ()
582
+ watcher , cs := helperCreateWatcherWithInformer (t )
583
+
584
+ var counter atomic.Int32
585
+ cs .AddEventHandler (ctx , func () {
586
+ counter .Add (1 )
587
+ })
588
+
589
+ obj := & unstructured.Unstructured {}
590
+ obj .SetName ("test" )
591
+
592
+ watcher .Delete (obj )
593
+
594
+ require .Eventually (t , func () bool {
595
+ return counter .Load () == 1
596
+ }, time .Second , 10 * time .Millisecond )
597
+ }
598
+
599
+ func TestCRDSource_Watch (t * testing.T ) {
600
+ scheme := runtime .NewScheme ()
601
+ err := apiv1alpha1 .AddToScheme (scheme )
602
+ require .NoError (t , err )
603
+
604
+ var watchCalled bool
605
+
606
+ codecFactory := serializer.WithoutConversionCodecFactory {
607
+ CodecFactory : serializer .NewCodecFactory (scheme ),
608
+ }
609
+
610
+ versionApiPath := fmt .Sprintf ("/apis/%s" , apiv1alpha1 .GroupVersion .String ())
611
+
612
+ client := & fake.RESTClient {
613
+ GroupVersion : apiv1alpha1 .GroupVersion ,
614
+ VersionedAPIPath : versionApiPath ,
615
+ NegotiatedSerializer : codecFactory ,
616
+ Client : fake .CreateHTTPClient (func (req * http.Request ) (* http.Response , error ) {
617
+ if req .URL .Path == fmt .Sprintf ("%s/namespaces/test-ns/dnsendpoints" , versionApiPath ) &&
618
+ req .URL .Query ().Get ("watch" ) == "true" {
619
+ watchCalled = true
620
+ return & http.Response {
621
+ StatusCode : http .StatusOK ,
622
+ Header : make (http.Header ),
623
+ }, nil
624
+ }
625
+ t .Errorf ("unexpected request: %v" , req .URL )
626
+ return nil , fmt .Errorf ("unexpected request: %v" , req .URL )
627
+ }),
628
+ }
629
+
630
+ cs := & crdSource {
631
+ crdClient : client ,
632
+ namespace : "test-ns" ,
633
+ crdResource : "dnsendpoints" ,
634
+ codec : runtime .NewParameterCodec (scheme ),
635
+ }
636
+
637
+ opts := & metav1.ListOptions {}
638
+
639
+ _ , err = cs .watch (t .Context (), opts )
640
+ require .NoError (t , err )
641
+ require .True (t , watchCalled )
642
+ require .True (t , opts .Watch )
643
+ }
644
+
514
645
func validateCRDResource (t * testing.T , src Source , expectError bool ) {
646
+ t .Helper ()
515
647
cs := src .(* crdSource )
516
648
result , err := cs .List (context .Background (), & metav1.ListOptions {})
517
649
if expectError {
@@ -526,3 +658,24 @@ func validateCRDResource(t *testing.T, src Source, expectError bool) {
526
658
}
527
659
}
528
660
}
661
+
662
+ func helperCreateWatcherWithInformer (t * testing.T ) (* cachetesting.FakeControllerSource , crdSource ) {
663
+ t .Helper ()
664
+ ctx := t .Context ()
665
+
666
+ watcher := cachetesting .NewFakeControllerSource ()
667
+
668
+ informer := cache .NewSharedInformer (watcher , & unstructured.Unstructured {}, 0 )
669
+
670
+ go informer .RunWithContext (ctx )
671
+
672
+ require .Eventually (t , func () bool {
673
+ return cache .WaitForCacheSync (ctx .Done (), informer .HasSynced )
674
+ }, time .Second , 10 * time .Millisecond )
675
+
676
+ cs := & crdSource {
677
+ informer : & informer ,
678
+ }
679
+
680
+ return watcher , * cs
681
+ }
0 commit comments