@@ -24,7 +24,9 @@ import (
2424 . "github.com/onsi/gomega"
2525 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2626 "k8s.io/apimachinery/pkg/types"
27+ "k8s.io/utils/ptr"
2728
29+ clusterv1beta1 "github.com/kubefleet-dev/kubefleet/apis/cluster/v1beta1"
2830 placementv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1"
2931 "github.com/kubefleet-dev/kubefleet/pkg/controllers/workapplier"
3032)
@@ -373,3 +375,164 @@ var _ = Describe("mixed ClusterResourcePlacement and ResourcePlacement negative
373375 })
374376 })
375377})
378+
379+ var _ = Describe ("mixed ClusterResourcePlacement and ResourcePlacement positive test cases" , Label ("resourceplacement" ), func () {
380+ crpName := fmt .Sprintf (crpNameTemplate , GinkgoParallelProcess ())
381+ rpName := fmt .Sprintf (rpNameTemplate , GinkgoParallelProcess ())
382+ workNamespaceName := fmt .Sprintf (workNamespaceNameTemplate , GinkgoParallelProcess ())
383+ wantSelectedClusters := []string {memberCluster3WestProdName }
384+ wantUnscheduledClusters := []string {memberCluster1EastProdName , memberCluster2EastCanaryName }
385+
386+ Context ("coupling CRP and RP using cluster labeling" , Ordered , func () {
387+ BeforeAll (func () {
388+ By ("creating work resources" )
389+ createWorkResources ()
390+ })
391+
392+ AfterAll (func () {
393+ ensureRPAndRelatedResourcesDeleted (types.NamespacedName {Name : rpName , Namespace : workNamespaceName }, allMemberClusters )
394+ ensureCRPAndRelatedResourcesDeleted (crpName , allMemberClusters )
395+
396+ By ("removing labels from member clusters" )
397+ Eventually (func () error {
398+ for _ , clusterName := range wantSelectedClusters {
399+ mc := & clusterv1beta1.MemberCluster {}
400+ if err := hubClient .Get (ctx , types.NamespacedName {Name : clusterName }, mc ); err != nil {
401+ return fmt .Errorf ("failed to get member cluster %s: %w" , clusterName , err )
402+ }
403+
404+ if mc .Labels != nil {
405+ delete (mc .Labels , workNamespaceName )
406+ if err := hubClient .Update (ctx , mc ); err != nil {
407+ return fmt .Errorf ("failed to update member cluster %s: %w" , clusterName , err )
408+ }
409+ }
410+ }
411+ return nil
412+ }, eventuallyDuration , eventuallyInterval ).Should (Succeed (), "Failed to remove labels from member clusters" )
413+ })
414+
415+ It ("picking N clusters with no affinities/topology spread constraints (pick by cluster names in alphanumeric order)" , func () {
416+ crp := & placementv1beta1.ClusterResourcePlacement {
417+ ObjectMeta : metav1.ObjectMeta {
418+ Name : crpName ,
419+ // Add a custom finalizer; this would allow us to better observe
420+ // the behavior of the controllers.
421+ Finalizers : []string {customDeletionBlockerFinalizer },
422+ },
423+ Spec : placementv1beta1.PlacementSpec {
424+ ResourceSelectors : namespaceOnlySelector (),
425+ Policy : & placementv1beta1.PlacementPolicy {
426+ PlacementType : placementv1beta1 .PickNPlacementType ,
427+ NumberOfClusters : ptr .To (int32 (1 )),
428+ },
429+ Strategy : placementv1beta1.RolloutStrategy {
430+ Type : placementv1beta1 .RollingUpdateRolloutStrategyType ,
431+ RollingUpdate : & placementv1beta1.RollingUpdateConfig {
432+ UnavailablePeriodSeconds : ptr .To (2 ),
433+ },
434+ },
435+ },
436+ }
437+ Expect (hubClient .Create (ctx , crp )).To (Succeed (), "Failed to create CRP %s" , crpName )
438+ })
439+
440+ It ("should update CRP status as expected" , func () {
441+ crpStatusUpdatedActual := crpStatusUpdatedActual (workNamespaceIdentifiers (), wantSelectedClusters , nil , "0" )
442+ Eventually (crpStatusUpdatedActual , eventuallyDuration , eventuallyInterval ).Should (Succeed (), "Failed to update CRP status as expected" )
443+ })
444+
445+ It ("add labels to member clusters based on CRP placement decisions" , func () {
446+ Eventually (func () error {
447+ // Add labels to the selected clusters
448+ for _ , clusterName := range wantSelectedClusters {
449+ mc := & clusterv1beta1.MemberCluster {}
450+ if err := hubClient .Get (ctx , types.NamespacedName {Name : clusterName }, mc ); err != nil {
451+ return fmt .Errorf ("failed to get member cluster %s: %w" , clusterName , err )
452+ }
453+
454+ if mc .Labels == nil {
455+ mc .Labels = make (map [string ]string )
456+ }
457+ mc .Labels [workNamespaceName ] = "true"
458+
459+ if err := hubClient .Update (ctx , mc ); err != nil {
460+ return fmt .Errorf ("failed to update member cluster %s: %w" , clusterName , err )
461+ }
462+ }
463+ return nil
464+ }, eventuallyDuration , eventuallyInterval ).Should (Succeed (), "Failed to add labels to member clusters" )
465+ })
466+
467+ It ("should create an RP with pickN policy using cluster labels" , func () {
468+ rp := & placementv1beta1.ResourcePlacement {
469+ ObjectMeta : metav1.ObjectMeta {
470+ Name : rpName ,
471+ Namespace : workNamespaceName ,
472+ Finalizers : []string {customDeletionBlockerFinalizer },
473+ },
474+ Spec : placementv1beta1.PlacementSpec {
475+ ResourceSelectors : configMapSelector (),
476+ Policy : & placementv1beta1.PlacementPolicy {
477+ PlacementType : placementv1beta1 .PickNPlacementType ,
478+ NumberOfClusters : ptr .To (int32 (3 )),
479+ Affinity : & placementv1beta1.Affinity {
480+ ClusterAffinity : & placementv1beta1.ClusterAffinity {
481+ RequiredDuringSchedulingIgnoredDuringExecution : & placementv1beta1.ClusterSelector {
482+ ClusterSelectorTerms : []placementv1beta1.ClusterSelectorTerm {
483+ {
484+ LabelSelector : & metav1.LabelSelector {
485+ MatchLabels : map [string ]string {
486+ workNamespaceName : "true" ,
487+ },
488+ },
489+ },
490+ },
491+ },
492+ },
493+ },
494+ },
495+ },
496+ }
497+ Expect (hubClient .Create (ctx , rp )).To (Succeed (), "Failed to create RP %s" , rpName )
498+ })
499+
500+ It ("should update RP status as expected" , func () {
501+ rpStatusUpdatedActual := func () error {
502+ rpKey := types.NamespacedName {Name : rpName , Namespace : workNamespaceName }
503+ return customizedPlacementStatusUpdatedActual (rpKey , appConfigMapIdentifiers (), wantSelectedClusters , wantUnscheduledClusters , "0" , true )()
504+ }
505+ Eventually (rpStatusUpdatedActual , eventuallyDuration , eventuallyInterval ).Should (Succeed (), "Failed to update RP status as expected" )
506+ })
507+
508+ It ("should place resources on the picked clusters" , func () {
509+ resourcePlacedActual := workNamespaceAndConfigMapPlacedOnClusterActual (memberCluster3WestProd )
510+ Eventually (resourcePlacedActual , eventuallyDuration , eventuallyInterval ).Should (Succeed (), "Failed to place resources on the picked clusters" )
511+ })
512+
513+ It ("update RP to pick 1 cluster instead of 3" , func () {
514+ Eventually (func () error {
515+ rp := & placementv1beta1.ResourcePlacement {}
516+ if err := hubClient .Get (ctx , types.NamespacedName {Name : rpName , Namespace : workNamespaceName }, rp ); err != nil {
517+ return fmt .Errorf ("failed to get RP %s: %w" , rpName , err )
518+ }
519+
520+ rp .Spec .Policy .NumberOfClusters = ptr .To (int32 (1 ))
521+ if err := hubClient .Update (ctx , rp ); err != nil {
522+ return fmt .Errorf ("failed to update RP %s: %w" , rpName , err )
523+ }
524+ return nil
525+ }, eventuallyDuration , eventuallyInterval ).Should (Succeed (), "Failed to update RP to pick 1 cluster" )
526+ })
527+
528+ It ("should update RP status as expected" , func () {
529+ rpStatusUpdatedActual := rpStatusUpdatedActual (appConfigMapIdentifiers (), wantSelectedClusters , nil , "0" )
530+ Eventually (rpStatusUpdatedActual , eventuallyDuration , eventuallyInterval ).Should (Succeed (), "Failed to update RP status as expected" )
531+ })
532+
533+ It ("should still place resources only on the selected cluster" , func () {
534+ resourcePlacedActual := workNamespaceAndConfigMapPlacedOnClusterActual (memberCluster3WestProd )
535+ Eventually (resourcePlacedActual , eventuallyDuration , eventuallyInterval ).Should (Succeed (), "Failed to place resources on the selected cluster after update" )
536+ })
537+ })
538+ })
0 commit comments