@@ -23,6 +23,7 @@ import (
2323 "fmt"
2424 "strconv"
2525 "testing"
26+ "time"
2627
2728 "github.com/google/go-cmp/cmp"
2829 "github.com/google/go-cmp/cmp/cmpopts"
@@ -2390,3 +2391,211 @@ func TestDetermineRolloutStateForPlacementWithExternalRolloutStrategy(t *testing
23902391 })
23912392 }
23922393}
2394+
2395+ func TestHandleResourceSnapshotByStrategy (t * testing.T ) {
2396+ tests := []struct {
2397+ name string
2398+ crp * fleetv1beta1.ClusterResourcePlacement
2399+ existingSnapshots []client.Object
2400+ selectedResources []fleetv1beta1.ResourceContent
2401+ selectedResourceIDs []fleetv1beta1.ResourceIdentifier
2402+ snapshotResolverConfig * controller.ResourceSnapshotConfig // optional Config for the resolver
2403+ wantSnapshot bool
2404+ wantSnapshotName string
2405+ wantSelectedResourceIDs []fleetv1beta1.ResourceIdentifier
2406+ wantRequeueAfter bool // true if we expect RequeueAfter > 0
2407+ wantErr bool
2408+ }{
2409+ {
2410+ name : "External rollout strategy with no existing snapshot" ,
2411+ crp : & fleetv1beta1.ClusterResourcePlacement {
2412+ ObjectMeta : metav1.ObjectMeta {
2413+ Name : testCRPName ,
2414+ Generation : 1 ,
2415+ },
2416+ Spec : fleetv1beta1.PlacementSpec {
2417+ ResourceSelectors : []fleetv1beta1.ResourceSelectorTerm {
2418+ {
2419+ Group : corev1 .GroupName ,
2420+ Version : "v1" ,
2421+ Kind : "Namespace" ,
2422+ },
2423+ },
2424+ Strategy : fleetv1beta1.RolloutStrategy {
2425+ Type : fleetv1beta1 .ExternalRolloutStrategyType ,
2426+ },
2427+ },
2428+ },
2429+ existingSnapshots : []client.Object {},
2430+ selectedResources : []fleetv1beta1.ResourceContent {},
2431+ selectedResourceIDs : []fleetv1beta1.ResourceIdentifier {{Kind : "Namespace" , Name : "test" }},
2432+ wantSnapshot : false ,
2433+ wantSnapshotName : "" ,
2434+ wantSelectedResourceIDs : []fleetv1beta1.ResourceIdentifier {{Kind : "Namespace" , Name : "test" }},
2435+ wantRequeueAfter : false ,
2436+ wantErr : false ,
2437+ },
2438+ {
2439+ name : "RollingUpdate strategy creates new snapshot when none exists" ,
2440+ crp : & fleetv1beta1.ClusterResourcePlacement {
2441+ ObjectMeta : metav1.ObjectMeta {
2442+ Name : testCRPName ,
2443+ Generation : 1 ,
2444+ },
2445+ Spec : fleetv1beta1.PlacementSpec {
2446+ ResourceSelectors : []fleetv1beta1.ResourceSelectorTerm {
2447+ {
2448+ Group : corev1 .GroupName ,
2449+ Version : "v1" ,
2450+ Kind : "Namespace" ,
2451+ },
2452+ },
2453+ Strategy : fleetv1beta1.RolloutStrategy {
2454+ Type : fleetv1beta1 .RollingUpdateRolloutStrategyType ,
2455+ },
2456+ },
2457+ },
2458+ existingSnapshots : []client.Object {},
2459+ selectedResources : []fleetv1beta1.ResourceContent {
2460+ {
2461+ RawExtension : runtime.RawExtension {
2462+ Raw : []byte (`{"apiVersion":"v1","kind":"Namespace","metadata":{"name":"test-ns"}}` ),
2463+ },
2464+ },
2465+ },
2466+ selectedResourceIDs : []fleetv1beta1.ResourceIdentifier {{Kind : "Namespace" , Name : "test-ns" }},
2467+ wantSnapshot : true ,
2468+ wantSnapshotName : fmt .Sprintf (fleetv1beta1 .ResourceSnapshotNameFmt , testCRPName , 0 ),
2469+ wantSelectedResourceIDs : []fleetv1beta1.ResourceIdentifier {{Kind : "Namespace" , Name : "test-ns" }},
2470+ wantRequeueAfter : false ,
2471+ wantErr : false ,
2472+ },
2473+ {
2474+ name : "RollingUpdate strategy with different hash triggers requeue when interval configured" ,
2475+ crp : & fleetv1beta1.ClusterResourcePlacement {
2476+ ObjectMeta : metav1.ObjectMeta {
2477+ Name : testCRPName ,
2478+ Generation : 1 ,
2479+ },
2480+ Spec : fleetv1beta1.PlacementSpec {
2481+ ResourceSelectors : []fleetv1beta1.ResourceSelectorTerm {
2482+ {
2483+ Group : corev1 .GroupName ,
2484+ Version : "v1" ,
2485+ Kind : "Namespace" ,
2486+ },
2487+ },
2488+ Strategy : fleetv1beta1.RolloutStrategy {
2489+ Type : fleetv1beta1 .RollingUpdateRolloutStrategyType ,
2490+ },
2491+ },
2492+ },
2493+ existingSnapshots : []client.Object {
2494+ & fleetv1beta1.ClusterResourceSnapshot {
2495+ ObjectMeta : metav1.ObjectMeta {
2496+ Name : fmt .Sprintf (fleetv1beta1 .ResourceSnapshotNameFmt , testCRPName , 0 ),
2497+ CreationTimestamp : metav1 .Now (),
2498+ Labels : map [string ]string {
2499+ fleetv1beta1 .PlacementTrackingLabel : testCRPName ,
2500+ fleetv1beta1 .IsLatestSnapshotLabel : strconv .FormatBool (true ),
2501+ fleetv1beta1 .ResourceIndexLabel : "0" ,
2502+ },
2503+ Annotations : map [string ]string {
2504+ fleetv1beta1 .ResourceGroupHashAnnotation : "old-hash-different-from-new" ,
2505+ fleetv1beta1 .NumberOfResourceSnapshotsAnnotation : "1" ,
2506+ },
2507+ },
2508+ Spec : fleetv1beta1.ResourceSnapshotSpec {
2509+ SelectedResources : []fleetv1beta1.ResourceContent {
2510+ {
2511+ RawExtension : runtime.RawExtension {
2512+ Raw : []byte (`{"apiVersion":"v1","kind":"Namespace","metadata":{"name":"old-ns"}}` ),
2513+ },
2514+ },
2515+ },
2516+ },
2517+ },
2518+ },
2519+ selectedResources : []fleetv1beta1.ResourceContent {
2520+ {
2521+ RawExtension : runtime.RawExtension {
2522+ Raw : []byte (`{"apiVersion":"v1","kind":"Namespace","metadata":{"name":"new-ns"}}` ),
2523+ },
2524+ },
2525+ },
2526+ selectedResourceIDs : []fleetv1beta1.ResourceIdentifier {{Kind : "Namespace" , Name : "new-ns" }},
2527+ snapshotResolverConfig : controller .NewResourceSnapshotConfig (15 * time .Second , 10 * time .Second ),
2528+ wantSnapshot : true ,
2529+ wantSnapshotName : fmt .Sprintf (fleetv1beta1 .ResourceSnapshotNameFmt , testCRPName , 0 ),
2530+ // When requeue is triggered, selectedResourceIDs are rebuilt from the existing snapshot
2531+ wantSelectedResourceIDs : []fleetv1beta1.ResourceIdentifier {
2532+ {
2533+ Group : "" ,
2534+ Version : "v1" ,
2535+ Kind : "Namespace" ,
2536+ Name : "old-ns" ,
2537+ },
2538+ },
2539+ wantRequeueAfter : true ,
2540+ wantErr : false ,
2541+ },
2542+ }
2543+
2544+ for _ , tc := range tests {
2545+ t .Run (tc .name , func (t * testing.T ) {
2546+ scheme := serviceScheme (t )
2547+ fakeClient := fake .NewClientBuilder ().
2548+ WithScheme (scheme ).
2549+ WithObjects (tc .existingSnapshots ... ).
2550+ Build ()
2551+ resolver := controller .NewResourceSnapshotResolver (fakeClient , scheme )
2552+ if tc .snapshotResolverConfig != nil {
2553+ resolver .Config = tc .snapshotResolverConfig
2554+ }
2555+ r := Reconciler {
2556+ Client : fakeClient ,
2557+ Scheme : scheme ,
2558+ ResourceSnapshotResolver : resolver ,
2559+ }
2560+
2561+ gotResult , gotSnapshot , gotSelectedResourceIDs , gotErr := r .handleResourceSnapshotByStrategy (
2562+ context .Background (), tc .crp , 0 , tc .selectedResources , tc .selectedResourceIDs , 10 )
2563+
2564+ if (gotErr != nil ) != tc .wantErr {
2565+ t .Errorf ("handleResourceSnapshotByStrategy() error = %v, wantErr %v" , gotErr , tc .wantErr )
2566+ return
2567+ }
2568+
2569+ if tc .wantSnapshot {
2570+ if gotSnapshot == nil {
2571+ t .Errorf ("handleResourceSnapshotByStrategy() gotSnapshot = nil, want non-nil" )
2572+ return
2573+ }
2574+ if gotSnapshot .GetName () != tc .wantSnapshotName {
2575+ t .Errorf ("handleResourceSnapshotByStrategy() gotSnapshot.Name = %v, want %v" , gotSnapshot .GetName (), tc .wantSnapshotName )
2576+ }
2577+ } else {
2578+ if gotSnapshot != nil {
2579+ t .Errorf ("handleResourceSnapshotByStrategy() gotSnapshot = %v, want nil" , gotSnapshot .GetName ())
2580+ }
2581+ }
2582+
2583+ if diff := cmp .Diff (tc .wantSelectedResourceIDs , gotSelectedResourceIDs ); diff != "" {
2584+ t .Errorf ("handleResourceSnapshotByStrategy() selectedResourceIDs mismatch (-want, +got):\n %s" , diff )
2585+ }
2586+
2587+ // Verify RequeueAfter behavior
2588+ gotRequeueAfter := gotResult .RequeueAfter > 0
2589+ if gotRequeueAfter != tc .wantRequeueAfter {
2590+ t .Errorf ("handleResourceSnapshotByStrategy() gotResult.RequeueAfter > 0 = %v, want %v" , gotRequeueAfter , tc .wantRequeueAfter )
2591+ }
2592+
2593+ // For External strategy, we always expect no requeue
2594+ if tc .crp .Spec .Strategy .Type == fleetv1beta1 .ExternalRolloutStrategyType {
2595+ if gotResult .RequeueAfter != 0 {
2596+ t .Errorf ("handleResourceSnapshotByStrategy() gotResult.RequeueAfter = %v, want 0 for External strategy" , gotResult .RequeueAfter )
2597+ }
2598+ }
2599+ })
2600+ }
2601+ }
0 commit comments