@@ -30,6 +30,7 @@ import (
30
30
"github.com/kcp-dev/logicalcluster/v3"
31
31
"github.com/stretchr/testify/require"
32
32
33
+ rbacv1 "k8s.io/api/rbac/v1"
33
34
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
34
35
"k8s.io/apimachinery/pkg/api/errors"
35
36
"k8s.io/apimachinery/pkg/api/meta"
@@ -64,6 +65,10 @@ var scenarios = []testScenario{
64
65
{"TestReplicateAPIResourceSchemaNegative" , replicateAPIResourceSchemaNegativeScenario },
65
66
{"TestReplicateWorkspaceType" , replicateWorkspaceTypeScenario },
66
67
{"TestReplicateWorkspaceTypeNegative" , replicateWorkspaceTypeNegativeScenario },
68
+ {"TestReplicateWorkloadsClusterRole" , replicateWorkloadsClusterRoleScenario },
69
+ {"TestReplicateWorkloadsClusterRoleNegative" , replicateWorkloadsClusterRoleNegativeScenario },
70
+ {"TestReplicateWorkloadsClusterRoleBinding" , replicateWorkloadsClusterRoleBindingScenario },
71
+ {"TestReplicateWorkloadsClusterRoleBindingNegative" , replicateWorkloadsClusterRoleBindingNegativeScenario },
67
72
}
68
73
69
74
// disruptiveScenarios contains a list of scenarios that will be run in a private environment
@@ -330,7 +335,9 @@ func replicateResource(ctx context.Context, t *testing.T,
330
335
kind string , /*kind for the given resource*/
331
336
gvr schema.GroupVersionResource , /*gvr for the given resource*/
332
337
res runtime.Object , /*a strongly typed resource object that will be created*/
333
- resWithModifiedSpec runtime.Object /*a strongly typed resource obj with modified spec only, will be used for an update*/ ) {
338
+ resWithModifiedSpec runtime.Object , /*a strongly typed resource obj with modified spec only, will be used for an update*/
339
+ prepares ... func (* replicateResourceScenario ), /*additional functions that allow preparing the context of the source resource before expecting replication*/
340
+ ) {
334
341
t .Helper ()
335
342
336
343
orgPath , _ := framework .NewOrganizationFixture (t , server )
@@ -343,6 +350,10 @@ func replicateResource(ctx context.Context, t *testing.T,
343
350
resourceName := resMeta .GetName ()
344
351
scenario := & replicateResourceScenario {resourceName : resourceName , kind : kind , gvr : gvr , cluster : clusterName , server : server , kcpShardClusterDynamicClient : kcpShardClusterDynamicClient , cacheKcpClusterDynamicClient : cacheKcpClusterDynamicClient }
345
352
353
+ for _ , prepare := range prepares {
354
+ prepare (scenario )
355
+ }
356
+
346
357
t .Logf ("Create source %s %s/%s on the root shard for replication" , kind , clusterName , resourceName )
347
358
scenario .CreateSourceResource (ctx , t , res )
348
359
t .Logf ("Verify that the source %s %s/%s was replicated to the cache server" , kind , clusterName , resourceName )
@@ -383,7 +394,9 @@ func replicateResourceNegative(ctx context.Context, t *testing.T,
383
394
kind string , /*kind for the given resource*/
384
395
gvr schema.GroupVersionResource , /*gvr for the given resource*/
385
396
res runtime.Object , /*a strongly typed resource object that will be created*/
386
- resWithModifiedSpec runtime.Object /*a strongly typed resource obj with modified spec only, will be used for an update*/ ) {
397
+ resWithModifiedSpec runtime.Object , /*a strongly typed resource obj with modified spec only, will be used for an update*/
398
+ prepares ... func (* replicateResourceScenario ), /*additional functions that allow preparing the context of the source resource before expecting replication*/
399
+ ) {
387
400
t .Helper ()
388
401
389
402
orgPath , _ := framework .NewOrganizationFixture (t , server )
@@ -396,6 +409,10 @@ func replicateResourceNegative(ctx context.Context, t *testing.T,
396
409
resourceName := resMeta .GetName ()
397
410
scenario := & replicateResourceScenario {resourceName : resourceName , kind : kind , gvr : gvr , cluster : clusterName , server : server , kcpShardClusterDynamicClient : kcpShardClusterDynamicClient , cacheKcpClusterDynamicClient : cacheKcpClusterDynamicClient }
398
411
412
+ for _ , prepare := range prepares {
413
+ prepare (scenario )
414
+ }
415
+
399
416
t .Logf ("Create source %s %s/%s on the root shard for replication" , kind , clusterName , resourceName )
400
417
scenario .CreateSourceResource (ctx , t , res )
401
418
t .Logf ("Verify that the source %s %s/%s was replicated to the cache server" , kind , clusterName , resourceName )
@@ -486,6 +503,14 @@ type replicateResourceScenario struct {
486
503
cacheKcpClusterDynamicClient kcpdynamic.ClusterInterface
487
504
}
488
505
506
+ func (b * replicateResourceScenario ) CreateAdditionalResource (ctx context.Context , t * testing.T , res runtime.Object , kind string , gvr schema.GroupVersionResource ) {
507
+ t .Helper ()
508
+ resUnstructured , err := toUnstructured (res , kind , gvr )
509
+ require .NoError (t , err )
510
+ _ , err = b .kcpShardClusterDynamicClient .Resource (gvr ).Cluster (b .cluster .Path ()).Create (ctx , resUnstructured , metav1.CreateOptions {})
511
+ require .NoError (t , err )
512
+ }
513
+
489
514
func (b * replicateResourceScenario ) CreateSourceResource (ctx context.Context , t * testing.T , res runtime.Object ) {
490
515
t .Helper ()
491
516
resUnstructured , err := toUnstructured (res , b .kind , b .gvr )
@@ -678,14 +703,22 @@ func (b *replicateResourceScenario) verifyResourceReplicationHelper(ctx context.
678
703
}
679
704
unstructured .RemoveNestedField (originalResource .Object , "metadata" , "resourceVersion" )
680
705
unstructured .RemoveNestedField (cachedResource .Object , "metadata" , "resourceVersion" )
706
+
707
+ // TODO(davidfestal): find out why the generation is not the same especially for rbacv1. Is it a characteristic of all
708
+ // internal KCP resources (which are not backed by CRDs) ?
709
+ if b .gvr .Group == rbacv1 .SchemeGroupVersion .Group {
710
+ unstructured .RemoveNestedField (originalResource .Object , "metadata" , "generation" )
711
+ unstructured .RemoveNestedField (cachedResource .Object , "metadata" , "generation" )
712
+ }
713
+
681
714
unstructured .RemoveNestedField (cachedResource .Object , "metadata" , "annotations" , genericapirequest .AnnotationKey )
682
715
if cachedStatus , ok := cachedResource .Object ["status" ]; ok && cachedStatus == nil || (cachedStatus != nil && len (cachedStatus .(map [string ]interface {})) == 0 ) {
683
716
// TODO: worth investigating:
684
717
// for some reason cached resources have an empty status set whereas the original resources don't
685
718
unstructured .RemoveNestedField (cachedResource .Object , "status" )
686
719
}
687
720
if diff := cmp .Diff (cachedResource .Object , originalResource .Object ); len (diff ) > 0 {
688
- return false , fmt .Sprintf ("replicated %s root|%s/%s is different from the original" , b .gvr , cluster , cachedResourceMeta .GetName ())
721
+ return false , fmt .Sprintf ("replicated %s root|%s/%s is different from the original: %s " , b .gvr , cluster , cachedResourceMeta .GetName (), diff )
689
722
}
690
723
return true , ""
691
724
}, wait .ForeverTestTimeout , 100 * time .Millisecond )
@@ -732,3 +765,167 @@ func createCacheClientConfigForEnvironment(ctx context.Context, t *testing.T, kc
732
765
require .NoError (t , err )
733
766
return cacheServerRestConfig
734
767
}
768
+
769
+ // replicateWorkloadsClusterRoleScenario tests if a ClusterRole related to workloads API is propagated to the cache server.
770
+ // The test exercises creation, modification and removal of the Shard object.
771
+ func replicateWorkloadsClusterRoleScenario (ctx context.Context , t * testing.T , server framework.RunningServer , kcpShardClusterDynamicClient kcpdynamic.ClusterInterface , cacheKcpClusterDynamicClient kcpdynamic.ClusterInterface ) {
772
+ t .Helper ()
773
+ replicateResource (ctx ,
774
+ t ,
775
+ server ,
776
+ kcpShardClusterDynamicClient ,
777
+ cacheKcpClusterDynamicClient ,
778
+ "" ,
779
+ "ClusterRole" ,
780
+ rbacv1 .SchemeGroupVersion .WithResource ("clusterroles" ),
781
+ & rbacv1.ClusterRole {
782
+ ObjectMeta : metav1.ObjectMeta {
783
+ Name : withPseudoRandomSuffix ("syncer" ),
784
+ },
785
+ Rules : []rbacv1.PolicyRule {
786
+ {
787
+ Verbs : []string {"sync" },
788
+ APIGroups : []string {"workload.kcp.io" },
789
+ Resources : []string {"synctargets" },
790
+ ResourceNames : []string {"asynctarget" },
791
+ },
792
+ },
793
+ },
794
+ nil ,
795
+ )
796
+ }
797
+
798
+ // replicateWorkloadsClusterRoleNegativeScenario checks if modified or even deleted cached ClusterRole (related to workloads API) will be reconciled to match the original object.
799
+ func replicateWorkloadsClusterRoleNegativeScenario (ctx context.Context , t * testing.T , server framework.RunningServer , kcpShardClusterDynamicClient kcpdynamic.ClusterInterface , cacheKcpClusterDynamicClient kcpdynamic.ClusterInterface ) {
800
+ t .Helper ()
801
+ replicateResourceNegative (
802
+ ctx ,
803
+ t ,
804
+ server ,
805
+ kcpShardClusterDynamicClient ,
806
+ cacheKcpClusterDynamicClient ,
807
+ "" ,
808
+ "ClusterRole" ,
809
+ rbacv1 .SchemeGroupVersion .WithResource ("clusterroles" ),
810
+ & rbacv1.ClusterRole {
811
+ ObjectMeta : metav1.ObjectMeta {
812
+ Name : withPseudoRandomSuffix ("syncer" ),
813
+ },
814
+ Rules : []rbacv1.PolicyRule {
815
+ {
816
+ Verbs : []string {"sync" },
817
+ APIGroups : []string {"workload.kcp.io" },
818
+ Resources : []string {"synctargets" },
819
+ ResourceNames : []string {"asynctarget" },
820
+ },
821
+ },
822
+ },
823
+ nil ,
824
+ )
825
+ }
826
+
827
+ // replicateWorkloadsClusterRoleBindingScenario tests if a ClusterRoleBinding related to workloads API is propagated to the cache server.
828
+ // The test exercises creation, modification and removal of the Shard object.
829
+ func replicateWorkloadsClusterRoleBindingScenario (ctx context.Context , t * testing.T , server framework.RunningServer , kcpShardClusterDynamicClient kcpdynamic.ClusterInterface , cacheKcpClusterDynamicClient kcpdynamic.ClusterInterface ) {
830
+ t .Helper ()
831
+
832
+ clusterRole := & rbacv1.ClusterRole {
833
+ ObjectMeta : metav1.ObjectMeta {
834
+ Name : withPseudoRandomSuffix ("syncer" ),
835
+ },
836
+ Rules : []rbacv1.PolicyRule {
837
+ {
838
+ Verbs : []string {"sync" },
839
+ APIGroups : []string {"workload.kcp.io" },
840
+ Resources : []string {"synctargets" },
841
+ ResourceNames : []string {"asynctarget" },
842
+ },
843
+ },
844
+ }
845
+
846
+ replicateResource (ctx ,
847
+ t ,
848
+ server ,
849
+ kcpShardClusterDynamicClient ,
850
+ cacheKcpClusterDynamicClient ,
851
+ "" ,
852
+ "ClusterRoleBinding" ,
853
+ rbacv1 .SchemeGroupVersion .WithResource ("clusterrolebindings" ),
854
+ & rbacv1.ClusterRoleBinding {
855
+ ObjectMeta : metav1.ObjectMeta {
856
+ Name : withPseudoRandomSuffix ("syncer" ),
857
+ },
858
+ RoleRef : rbacv1.RoleRef {
859
+ APIGroup : rbacv1 .SchemeGroupVersion .Group ,
860
+ Kind : "ClusterRole" ,
861
+ Name : clusterRole .Name ,
862
+ },
863
+ Subjects : []rbacv1.Subject {
864
+ {
865
+ Kind : "ServiceAccount" ,
866
+ APIGroup : "" ,
867
+ Name : "kcp-syncer-0000" ,
868
+ Namespace : "kcp-syncer-namespace" ,
869
+ },
870
+ },
871
+ },
872
+ nil ,
873
+ func (scenario * replicateResourceScenario ) {
874
+ t .Logf ("Create additional ClusterRole %s on the root shard for replication" , clusterRole .Name )
875
+ scenario .CreateAdditionalResource (ctx , t , clusterRole , "ClusterRole" , rbacv1 .SchemeGroupVersion .WithResource ("clusterroles" ))
876
+ },
877
+ )
878
+ }
879
+
880
+ // replicateWorkloadsClusterRoleNegativeScenario checks if modified or even deleted cached ClusterRole (related to workloads API) will be reconciled to match the original object.
881
+ func replicateWorkloadsClusterRoleBindingNegativeScenario (ctx context.Context , t * testing.T , server framework.RunningServer , kcpShardClusterDynamicClient kcpdynamic.ClusterInterface , cacheKcpClusterDynamicClient kcpdynamic.ClusterInterface ) {
882
+ t .Helper ()
883
+
884
+ clusterRole := & rbacv1.ClusterRole {
885
+ ObjectMeta : metav1.ObjectMeta {
886
+ Name : withPseudoRandomSuffix ("syncer" ),
887
+ },
888
+ Rules : []rbacv1.PolicyRule {
889
+ {
890
+ Verbs : []string {"sync" },
891
+ APIGroups : []string {"workload.kcp.io" },
892
+ Resources : []string {"synctargets" },
893
+ ResourceNames : []string {"asynctarget" },
894
+ },
895
+ },
896
+ }
897
+
898
+ replicateResourceNegative (
899
+ ctx ,
900
+ t ,
901
+ server ,
902
+ kcpShardClusterDynamicClient ,
903
+ cacheKcpClusterDynamicClient ,
904
+ "" ,
905
+ "ClusterRoleBinding" ,
906
+ rbacv1 .SchemeGroupVersion .WithResource ("clusterrolebindings" ),
907
+ & rbacv1.ClusterRoleBinding {
908
+ ObjectMeta : metav1.ObjectMeta {
909
+ Name : withPseudoRandomSuffix ("syncer" ),
910
+ },
911
+ RoleRef : rbacv1.RoleRef {
912
+ APIGroup : rbacv1 .SchemeGroupVersion .Group ,
913
+ Kind : "ClusterRole" ,
914
+ Name : clusterRole .Name ,
915
+ },
916
+ Subjects : []rbacv1.Subject {
917
+ {
918
+ Kind : "ServiceAccount" ,
919
+ APIGroup : "" ,
920
+ Name : "kcp-syncer-0000" ,
921
+ Namespace : "kcp-syncer-namespace" ,
922
+ },
923
+ },
924
+ },
925
+ nil ,
926
+ func (scenario * replicateResourceScenario ) {
927
+ t .Logf ("Create additional ClusterRole %s on the root shard for replication" , clusterRole .Name )
928
+ scenario .CreateAdditionalResource (ctx , t , clusterRole , "ClusterRole" , rbacv1 .SchemeGroupVersion .WithResource ("clusterroles" ))
929
+ },
930
+ )
931
+ }
0 commit comments