@@ -28,9 +28,11 @@ import (
28
28
gomegatypes "github.com/onsi/gomega/types"
29
29
30
30
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
31
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
31
32
"k8s.io/apimachinery/pkg/api/meta"
32
33
"k8s.io/apimachinery/pkg/runtime/schema"
33
34
"k8s.io/apimachinery/pkg/types"
35
+ "k8s.io/client-go/discovery"
34
36
"k8s.io/client-go/kubernetes/scheme"
35
37
"k8s.io/client-go/rest"
36
38
@@ -529,23 +531,7 @@ func TestLazyRestMapperProvider(t *testing.T) {
529
531
g .Expect (err ).NotTo (gmg .HaveOccurred ())
530
532
531
533
// Register another CRD in runtime - "riders.crew.example.com".
532
-
533
- crd := & apiextensionsv1.CustomResourceDefinition {}
534
- err = c .Get (context .TODO (), types.NamespacedName {Name : "drivers.crew.example.com" }, crd )
535
- g .Expect (err ).NotTo (gmg .HaveOccurred ())
536
- g .Expect (crd .Spec .Names .Kind ).To (gmg .Equal ("Driver" ))
537
-
538
- newCRD := & apiextensionsv1.CustomResourceDefinition {}
539
- crd .DeepCopyInto (newCRD )
540
- newCRD .Name = "riders.crew.example.com"
541
- newCRD .Spec .Names = apiextensionsv1.CustomResourceDefinitionNames {
542
- Kind : "Rider" ,
543
- Plural : "riders" ,
544
- }
545
- newCRD .ResourceVersion = ""
546
-
547
- // Create the new CRD.
548
- g .Expect (c .Create (context .TODO (), newCRD )).To (gmg .Succeed ())
534
+ createNewCRD (context .TODO (), g , c , "crew.example.com" , "Rider" , "riders" )
549
535
550
536
// Wait a bit until the CRD is registered.
551
537
g .Eventually (func () error {
@@ -564,6 +550,131 @@ func TestLazyRestMapperProvider(t *testing.T) {
564
550
g .Expect (err ).NotTo (gmg .HaveOccurred ())
565
551
g .Expect (mapping .GroupVersionKind .Kind ).To (gmg .Equal ("rider" ))
566
552
})
553
+
554
+ t .Run ("LazyRESTMapper should invalidate the group cache if a version is not found" , func (t * testing.T ) {
555
+ g := gmg .NewWithT (t )
556
+ ctx := context .Background ()
557
+
558
+ httpClient , err := rest .HTTPClientFor (restCfg )
559
+ g .Expect (err ).NotTo (gmg .HaveOccurred ())
560
+
561
+ crt := newCountingRoundTripper (httpClient .Transport )
562
+ httpClient .Transport = crt
563
+
564
+ lazyRestMapper , err := apiutil .NewDynamicRESTMapper (restCfg , httpClient )
565
+ g .Expect (err ).NotTo (gmg .HaveOccurred ())
566
+
567
+ s := scheme .Scheme
568
+ err = apiextensionsv1 .AddToScheme (s )
569
+ g .Expect (err ).NotTo (gmg .HaveOccurred ())
570
+
571
+ c , err := client .New (restCfg , client.Options {Scheme : s })
572
+ g .Expect (err ).NotTo (gmg .HaveOccurred ())
573
+
574
+ // Register a new CRD ina new group to avoid collisions when deleting versions - "taxi.inventory.example.com".
575
+ group := "inventory.example.com"
576
+ kind := "Taxi"
577
+ plural := "taxis"
578
+ crdName := plural + "." + group
579
+ crd := createNewCRD (ctx , g , c , group , kind , plural )
580
+ t .Cleanup (func () {
581
+ g .Expect (c .Delete (ctx , crd )).To (gmg .Succeed ())
582
+ })
583
+
584
+ // Wait until the CRD is registered.
585
+ discHTTP , err := rest .HTTPClientFor (restCfg )
586
+ g .Expect (err ).NotTo (gmg .HaveOccurred ())
587
+ discClient , err := discovery .NewDiscoveryClientForConfigAndClient (restCfg , discHTTP )
588
+ g .Expect (err ).NotTo (gmg .HaveOccurred ())
589
+ g .Eventually (func (g gmg.Gomega ) {
590
+ _ , err = discClient .ServerResourcesForGroupVersion (group + "/v1" )
591
+ g .Expect (err ).NotTo (gmg .HaveOccurred ())
592
+ }).Should (gmg .Succeed (), "v1 should be available" )
593
+
594
+ // There are no requests before any call
595
+ g .Expect (crt .GetRequestCount ()).To (gmg .Equal (0 ))
596
+
597
+ // Since we don't specify what version we expect, restmapper will fetch them all and search there.
598
+ // To fetch a list of available versions
599
+ // #1: GET https://host/api
600
+ // #2: GET https://host/apis
601
+ // Then, for all available versions:
602
+ // #3: GET https://host/apis/inventory.example.com/v1
603
+ // #4: GET https://host/apis/inventory.example.com/v2
604
+ // This should fill the cache for apiGroups and versions.
605
+ mapping , err := lazyRestMapper .RESTMapping (schema.GroupKind {Group : group , Kind : kind })
606
+ g .Expect (err ).NotTo (gmg .HaveOccurred ())
607
+ g .Expect (mapping .GroupVersionKind .Kind ).To (gmg .Equal (kind ))
608
+ g .Expect (crt .GetRequestCount ()).To (gmg .Equal (4 ))
609
+ crt .Reset () // We reset the counter to check how many additional requests are made later.
610
+
611
+ // At this point v2 should be cached
612
+ _ , err = lazyRestMapper .RESTMapping (schema.GroupKind {Group : group , Kind : kind }, "v2" )
613
+ g .Expect (err ).NotTo (gmg .HaveOccurred ())
614
+ g .Expect (crt .GetRequestCount ()).To (gmg .Equal (0 ))
615
+
616
+ // We update the CRD to only have v1 version.
617
+ g .Expect (c .Get (ctx , types.NamespacedName {Name : crdName }, crd )).To (gmg .Succeed ())
618
+ var v1 apiextensionsv1.CustomResourceDefinitionVersion
619
+ for i , version := range crd .Spec .Versions {
620
+ if version .Name == "v1" {
621
+ crd .Spec .Versions [i ].Storage = true
622
+ v1 = version
623
+ v1 .Storage = true
624
+ }
625
+ }
626
+ crd .Spec .Versions = []apiextensionsv1.CustomResourceDefinitionVersion {v1 }
627
+ g .Expect (c .Update (ctx , crd )).To (gmg .Succeed ())
628
+
629
+ // We wait until v2 is not available anymore.
630
+ g .Eventually (func (g gmg.Gomega ) {
631
+ _ , err = discClient .ServerResourcesForGroupVersion (group + "/v2" )
632
+ g .Expect (apierrors .IsNotFound (err )).To (gmg .BeTrue (), "v2 should not be available anymore" )
633
+ }).Should (gmg .Succeed ())
634
+
635
+ // Although v2 is not available anymore, the cache is not invalidated yet so it should return a mapping.
636
+ _ , err = lazyRestMapper .RESTMapping (schema.GroupKind {Group : group , Kind : kind }, "v2" )
637
+ g .Expect (err ).NotTo (gmg .HaveOccurred ())
638
+ g .Expect (crt .GetRequestCount ()).To (gmg .Equal (0 ))
639
+
640
+ // We request Limo, which is not in the mapper because it doesn't exist.
641
+ // This will trigger a reload of the lazy mapper cache.
642
+ // Reloading the cache will read v2 again and since it's not available anymore, it should invalidate the cache.
643
+ // #1: GET https://host/apis/inventory.example.com/v1
644
+ // #2: GET https://host/apis/inventory.example.com/v2
645
+ _ , err = lazyRestMapper .RESTMapping (schema.GroupKind {Group : group , Kind : "Limo" })
646
+ g .Expect (err ).To (beNoMatchError ())
647
+ g .Expect (crt .GetRequestCount ()).To (gmg .Equal (2 ))
648
+ crt .Reset ()
649
+
650
+ // Now we request v2 again and it should return an error since the cache was invalidated.
651
+ // #1: GET https://host/apis/inventory.example.com/v2
652
+ _ , err = lazyRestMapper .RESTMapping (schema.GroupKind {Group : group , Kind : kind }, "v2" )
653
+ g .Expect (err ).To (beNoMatchError ())
654
+ g .Expect (crt .GetRequestCount ()).To (gmg .Equal (1 ))
655
+ })
656
+ }
657
+
658
+ func createNewCRD (ctx context.Context , g gmg.Gomega , c client.Client , group , kind , plural string ) * apiextensionsv1.CustomResourceDefinition {
659
+ crd := & apiextensionsv1.CustomResourceDefinition {}
660
+ err := c .Get (ctx , types.NamespacedName {Name : "drivers.crew.example.com" }, crd )
661
+ g .Expect (err ).NotTo (gmg .HaveOccurred ())
662
+ g .Expect (crd .Spec .Names .Kind ).To (gmg .Equal ("Driver" ))
663
+
664
+ newCRD := & apiextensionsv1.CustomResourceDefinition {}
665
+ crd .DeepCopyInto (newCRD )
666
+ newCRD .Spec .Group = group
667
+ newCRD .Name = plural + "." + group
668
+ newCRD .Spec .Names = apiextensionsv1.CustomResourceDefinitionNames {
669
+ Kind : kind ,
670
+ Plural : plural ,
671
+ }
672
+ newCRD .ResourceVersion = ""
673
+
674
+ // Create the new CRD.
675
+ g .Expect (c .Create (ctx , newCRD )).To (gmg .Succeed ())
676
+
677
+ return newCRD
567
678
}
568
679
569
680
func beNoMatchError () gomegatypes.GomegaMatcher {
@@ -594,6 +705,7 @@ func (e *errorMatcher) Match(actual interface{}) (success bool, err error) {
594
705
func (e * errorMatcher ) FailureMessage (actual interface {}) (message string ) {
595
706
return format .Message (actual , fmt .Sprintf ("to be %s error" , e .message ))
596
707
}
708
+
597
709
func (e * errorMatcher ) NegatedFailureMessage (actual interface {}) (message string ) {
598
710
return format .Message (actual , fmt .Sprintf ("not to be %s error" , e .message ))
599
711
}
0 commit comments