@@ -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,23 @@ 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 equal, specially for rbacv1.
708
+ // Is it a characteristic of all built-in KCP resources (which are not backed by CRDs) ?
709
+ // Issue opened: https://github.com/kcp-dev/kcp/issues/2935
710
+ if b .gvr .Group == rbacv1 .SchemeGroupVersion .Group {
711
+ unstructured .RemoveNestedField (originalResource .Object , "metadata" , "generation" )
712
+ unstructured .RemoveNestedField (cachedResource .Object , "metadata" , "generation" )
713
+ }
714
+
681
715
unstructured .RemoveNestedField (cachedResource .Object , "metadata" , "annotations" , genericapirequest .AnnotationKey )
682
716
if cachedStatus , ok := cachedResource .Object ["status" ]; ok && cachedStatus == nil || (cachedStatus != nil && len (cachedStatus .(map [string ]interface {})) == 0 ) {
683
717
// TODO: worth investigating:
684
718
// for some reason cached resources have an empty status set whereas the original resources don't
685
719
unstructured .RemoveNestedField (cachedResource .Object , "status" )
686
720
}
687
721
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 ())
722
+ return false , fmt .Sprintf ("replicated %s root|%s/%s is different from the original: %s " , b .gvr , cluster , cachedResourceMeta .GetName (), diff )
689
723
}
690
724
return true , ""
691
725
}, wait .ForeverTestTimeout , 100 * time .Millisecond )
@@ -732,3 +766,167 @@ func createCacheClientConfigForEnvironment(ctx context.Context, t *testing.T, kc
732
766
require .NoError (t , err )
733
767
return cacheServerRestConfig
734
768
}
769
+
770
+ // replicateWorkloadsClusterRoleScenario tests if a ClusterRole related to workloads API is propagated to the cache server.
771
+ // The test exercises creation, modification and removal of the Shard object.
772
+ func replicateWorkloadsClusterRoleScenario (ctx context.Context , t * testing.T , server framework.RunningServer , kcpShardClusterDynamicClient kcpdynamic.ClusterInterface , cacheKcpClusterDynamicClient kcpdynamic.ClusterInterface ) {
773
+ t .Helper ()
774
+ replicateResource (ctx ,
775
+ t ,
776
+ server ,
777
+ kcpShardClusterDynamicClient ,
778
+ cacheKcpClusterDynamicClient ,
779
+ "" ,
780
+ "ClusterRole" ,
781
+ rbacv1 .SchemeGroupVersion .WithResource ("clusterroles" ),
782
+ & rbacv1.ClusterRole {
783
+ ObjectMeta : metav1.ObjectMeta {
784
+ Name : withPseudoRandomSuffix ("syncer" ),
785
+ },
786
+ Rules : []rbacv1.PolicyRule {
787
+ {
788
+ Verbs : []string {"sync" },
789
+ APIGroups : []string {"workload.kcp.io" },
790
+ Resources : []string {"synctargets" },
791
+ ResourceNames : []string {"asynctarget" },
792
+ },
793
+ },
794
+ },
795
+ nil ,
796
+ )
797
+ }
798
+
799
+ // replicateWorkloadsClusterRoleNegativeScenario checks if modified or even deleted cached ClusterRole (related to workloads API) will be reconciled to match the original object.
800
+ func replicateWorkloadsClusterRoleNegativeScenario (ctx context.Context , t * testing.T , server framework.RunningServer , kcpShardClusterDynamicClient kcpdynamic.ClusterInterface , cacheKcpClusterDynamicClient kcpdynamic.ClusterInterface ) {
801
+ t .Helper ()
802
+ replicateResourceNegative (
803
+ ctx ,
804
+ t ,
805
+ server ,
806
+ kcpShardClusterDynamicClient ,
807
+ cacheKcpClusterDynamicClient ,
808
+ "" ,
809
+ "ClusterRole" ,
810
+ rbacv1 .SchemeGroupVersion .WithResource ("clusterroles" ),
811
+ & rbacv1.ClusterRole {
812
+ ObjectMeta : metav1.ObjectMeta {
813
+ Name : withPseudoRandomSuffix ("syncer" ),
814
+ },
815
+ Rules : []rbacv1.PolicyRule {
816
+ {
817
+ Verbs : []string {"sync" },
818
+ APIGroups : []string {"workload.kcp.io" },
819
+ Resources : []string {"synctargets" },
820
+ ResourceNames : []string {"asynctarget" },
821
+ },
822
+ },
823
+ },
824
+ nil ,
825
+ )
826
+ }
827
+
828
+ // replicateWorkloadsClusterRoleBindingScenario tests if a ClusterRoleBinding related to workloads API is propagated to the cache server.
829
+ // The test exercises creation, modification and removal of the Shard object.
830
+ func replicateWorkloadsClusterRoleBindingScenario (ctx context.Context , t * testing.T , server framework.RunningServer , kcpShardClusterDynamicClient kcpdynamic.ClusterInterface , cacheKcpClusterDynamicClient kcpdynamic.ClusterInterface ) {
831
+ t .Helper ()
832
+
833
+ clusterRole := & rbacv1.ClusterRole {
834
+ ObjectMeta : metav1.ObjectMeta {
835
+ Name : withPseudoRandomSuffix ("syncer" ),
836
+ },
837
+ Rules : []rbacv1.PolicyRule {
838
+ {
839
+ Verbs : []string {"sync" },
840
+ APIGroups : []string {"workload.kcp.io" },
841
+ Resources : []string {"synctargets" },
842
+ ResourceNames : []string {"asynctarget" },
843
+ },
844
+ },
845
+ }
846
+
847
+ replicateResource (ctx ,
848
+ t ,
849
+ server ,
850
+ kcpShardClusterDynamicClient ,
851
+ cacheKcpClusterDynamicClient ,
852
+ "" ,
853
+ "ClusterRoleBinding" ,
854
+ rbacv1 .SchemeGroupVersion .WithResource ("clusterrolebindings" ),
855
+ & rbacv1.ClusterRoleBinding {
856
+ ObjectMeta : metav1.ObjectMeta {
857
+ Name : withPseudoRandomSuffix ("syncer" ),
858
+ },
859
+ RoleRef : rbacv1.RoleRef {
860
+ APIGroup : rbacv1 .SchemeGroupVersion .Group ,
861
+ Kind : "ClusterRole" ,
862
+ Name : clusterRole .Name ,
863
+ },
864
+ Subjects : []rbacv1.Subject {
865
+ {
866
+ Kind : "ServiceAccount" ,
867
+ APIGroup : "" ,
868
+ Name : "kcp-syncer-0000" ,
869
+ Namespace : "kcp-syncer-namespace" ,
870
+ },
871
+ },
872
+ },
873
+ nil ,
874
+ func (scenario * replicateResourceScenario ) {
875
+ t .Logf ("Create additional ClusterRole %s on the root shard for replication" , clusterRole .Name )
876
+ scenario .CreateAdditionalResource (ctx , t , clusterRole , "ClusterRole" , rbacv1 .SchemeGroupVersion .WithResource ("clusterroles" ))
877
+ },
878
+ )
879
+ }
880
+
881
+ // replicateWorkloadsClusterRoleNegativeScenario checks if modified or even deleted cached ClusterRole (related to workloads API) will be reconciled to match the original object.
882
+ func replicateWorkloadsClusterRoleBindingNegativeScenario (ctx context.Context , t * testing.T , server framework.RunningServer , kcpShardClusterDynamicClient kcpdynamic.ClusterInterface , cacheKcpClusterDynamicClient kcpdynamic.ClusterInterface ) {
883
+ t .Helper ()
884
+
885
+ clusterRole := & rbacv1.ClusterRole {
886
+ ObjectMeta : metav1.ObjectMeta {
887
+ Name : withPseudoRandomSuffix ("syncer" ),
888
+ },
889
+ Rules : []rbacv1.PolicyRule {
890
+ {
891
+ Verbs : []string {"sync" },
892
+ APIGroups : []string {"workload.kcp.io" },
893
+ Resources : []string {"synctargets" },
894
+ ResourceNames : []string {"asynctarget" },
895
+ },
896
+ },
897
+ }
898
+
899
+ replicateResourceNegative (
900
+ ctx ,
901
+ t ,
902
+ server ,
903
+ kcpShardClusterDynamicClient ,
904
+ cacheKcpClusterDynamicClient ,
905
+ "" ,
906
+ "ClusterRoleBinding" ,
907
+ rbacv1 .SchemeGroupVersion .WithResource ("clusterrolebindings" ),
908
+ & rbacv1.ClusterRoleBinding {
909
+ ObjectMeta : metav1.ObjectMeta {
910
+ Name : withPseudoRandomSuffix ("syncer" ),
911
+ },
912
+ RoleRef : rbacv1.RoleRef {
913
+ APIGroup : rbacv1 .SchemeGroupVersion .Group ,
914
+ Kind : "ClusterRole" ,
915
+ Name : clusterRole .Name ,
916
+ },
917
+ Subjects : []rbacv1.Subject {
918
+ {
919
+ Kind : "ServiceAccount" ,
920
+ APIGroup : "" ,
921
+ Name : "kcp-syncer-0000" ,
922
+ Namespace : "kcp-syncer-namespace" ,
923
+ },
924
+ },
925
+ },
926
+ nil ,
927
+ func (scenario * replicateResourceScenario ) {
928
+ t .Logf ("Create additional ClusterRole %s on the root shard for replication" , clusterRole .Name )
929
+ scenario .CreateAdditionalResource (ctx , t , clusterRole , "ClusterRole" , rbacv1 .SchemeGroupVersion .WithResource ("clusterroles" ))
930
+ },
931
+ )
932
+ }
0 commit comments