@@ -20,6 +20,7 @@ import (
2020 "context"
2121 "fmt"
2222 "reflect"
23+ "strings"
2324 "testing"
2425 "time"
2526
@@ -34,6 +35,7 @@ import (
3435 "k8s.io/client-go/dynamic"
3536 dynamicfake "k8s.io/client-go/dynamic/fake"
3637 "k8s.io/client-go/kubernetes/scheme"
38+ "k8s.io/client-go/tools/record"
3739 "k8s.io/utils/ptr"
3840 "sigs.k8s.io/controller-runtime/pkg/client"
3941 "sigs.k8s.io/controller-runtime/pkg/client/fake"
@@ -42,6 +44,7 @@ import (
4244 configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
4345 policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
4446 workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
47+ "github.com/karmada-io/karmada/pkg/events"
4548 "github.com/karmada-io/karmada/pkg/util"
4649 "github.com/karmada-io/karmada/pkg/util/fedinformer/genericmanager"
4750 "github.com/karmada-io/karmada/pkg/util/fedinformer/keys"
@@ -2523,7 +2526,15 @@ func Test_createOrUpdateAttachedBinding(t *testing.T) {
25232526 },
25242527 },
25252528 }
2526- return fake .NewClientBuilder ().WithScheme (Scheme ).WithObjects (rb ).Build ()
2529+
2530+ // Add the referenced ResourceBindings that will be looked up during conflict checks.
2531+ ref1 := & workv1alpha2.ResourceBinding {ObjectMeta : metav1.ObjectMeta {Name : "default-binding-1" , Namespace : "default-1" }}
2532+ ref2 := & workv1alpha2.ResourceBinding {ObjectMeta : metav1.ObjectMeta {Name : "default-binding-2" , Namespace : "default-2" }}
2533+ ref3 := & workv1alpha2.ResourceBinding {ObjectMeta : metav1.ObjectMeta {Name : "default-binding-3" , Namespace : "default-3" }}
2534+ ref4 := & workv1alpha2.ResourceBinding {ObjectMeta : metav1.ObjectMeta {Name : "test-binding-1" , Namespace : "test-1" }}
2535+ ref5 := & workv1alpha2.ResourceBinding {ObjectMeta : metav1.ObjectMeta {Name : "test-binding-2" , Namespace : "test-2" }}
2536+
2537+ return fake .NewClientBuilder ().WithScheme (Scheme ).WithObjects (rb , ref1 , ref2 , ref3 , ref4 , ref5 ).Build ()
25272538 },
25282539 },
25292540 {
@@ -2826,7 +2837,15 @@ func Test_createOrUpdateAttachedBinding(t *testing.T) {
28262837 },
28272838 },
28282839 }
2829- return fake .NewClientBuilder ().WithScheme (Scheme ).WithObjects (rb ).Build ()
2840+
2841+ // Referenced ResourceBindings for conflict checks
2842+ ref1 := & workv1alpha2.ResourceBinding {ObjectMeta : metav1.ObjectMeta {Name : "default-binding-1" , Namespace : "default-1" }}
2843+ ref2 := & workv1alpha2.ResourceBinding {ObjectMeta : metav1.ObjectMeta {Name : "default-binding-2" , Namespace : "default-2" }}
2844+ ref3 := & workv1alpha2.ResourceBinding {ObjectMeta : metav1.ObjectMeta {Name : "default-binding-3" , Namespace : "default-3" }}
2845+ ref4 := & workv1alpha2.ResourceBinding {ObjectMeta : metav1.ObjectMeta {Name : "test-binding-1" , Namespace : "test-1" }}
2846+ ref5 := & workv1alpha2.ResourceBinding {ObjectMeta : metav1.ObjectMeta {Name : "test-binding-2" , Namespace : "test-2" }}
2847+
2848+ return fake .NewClientBuilder ().WithScheme (Scheme ).WithObjects (rb , ref1 , ref2 , ref3 , ref4 , ref5 ).Build ()
28302849 },
28312850 },
28322851 {
@@ -2933,7 +2952,12 @@ func Test_createOrUpdateAttachedBinding(t *testing.T) {
29332952 },
29342953 },
29352954 }
2936- return fake .NewClientBuilder ().WithScheme (Scheme ).WithObjects (rb ).Build ()
2955+
2956+ // Referenced ResourceBindings for conflict checks
2957+ ref1 := & workv1alpha2.ResourceBinding {ObjectMeta : metav1.ObjectMeta {Name : "default-binding-1" , Namespace : "default-1" }}
2958+ ref2 := & workv1alpha2.ResourceBinding {ObjectMeta : metav1.ObjectMeta {Name : "test-binding-1" , Namespace : "test-1" }}
2959+
2960+ return fake .NewClientBuilder ().WithScheme (Scheme ).WithObjects (rb , ref1 , ref2 ).Build ()
29372961 },
29382962 },
29392963 }
@@ -2962,6 +2986,87 @@ func Test_createOrUpdateAttachedBinding(t *testing.T) {
29622986 }
29632987}
29642988
2989+ func Test_createOrUpdateAttachedBinding_emitsConflictEvent (t * testing.T ) {
2990+ Scheme := runtime .NewScheme ()
2991+ utilruntime .Must (scheme .AddToScheme (Scheme ))
2992+ utilruntime .Must (workv1alpha2 .Install (Scheme ))
2993+
2994+ // Existing attached binding to be updated (generated by dependency mechanism: Placement == nil)
2995+ exist := & workv1alpha2.ResourceBinding {
2996+ ObjectMeta : metav1.ObjectMeta {
2997+ Name : "test-binding" ,
2998+ Namespace : "test" ,
2999+ ResourceVersion : "1000" ,
3000+ OwnerReferences : []metav1.OwnerReference {{
3001+ UID : "uid-1" ,
3002+ Controller : ptr.To [bool ](true ),
3003+ }},
3004+ },
3005+ Spec : workv1alpha2.ResourceBindingSpec {},
3006+ }
3007+
3008+ // Referenced ResourceBindings with conflicting policies
3009+ rb1 := & workv1alpha2.ResourceBinding {
3010+ ObjectMeta : metav1.ObjectMeta {Name : "rb-1" , Namespace : "ns-a" },
3011+ Spec : workv1alpha2.ResourceBindingSpec {
3012+ PreserveResourcesOnDeletion : ptr .To (true ),
3013+ ConflictResolution : policyv1alpha1 .ConflictOverwrite ,
3014+ },
3015+ }
3016+ rb2 := & workv1alpha2.ResourceBinding {
3017+ ObjectMeta : metav1.ObjectMeta {Name : "rb-2" , Namespace : "ns-b" },
3018+ Spec : workv1alpha2.ResourceBindingSpec {
3019+ PreserveResourcesOnDeletion : ptr .To (false ),
3020+ },
3021+ }
3022+
3023+ attached := & workv1alpha2.ResourceBinding {
3024+ ObjectMeta : metav1.ObjectMeta {
3025+ Name : "test-binding" ,
3026+ Namespace : "test" ,
3027+ OwnerReferences : []metav1.OwnerReference {{
3028+ UID : "uid-1" ,
3029+ Controller : ptr.To [bool ](true ),
3030+ }},
3031+ },
3032+ Spec : workv1alpha2.ResourceBindingSpec {
3033+ Resource : workv1alpha2.ObjectReference {APIVersion : "apps/v1" , Kind : "Deployment" , Namespace : "fake-ns" , Name : "demo-app" , ResourceVersion : "1" },
3034+ RequiredBy : []workv1alpha2.BindingSnapshot {
3035+ {Namespace : "ns-a" , Name : "rb-1" },
3036+ {Namespace : "ns-b" , Name : "rb-2" },
3037+ },
3038+ },
3039+ }
3040+
3041+ fakeRec := record .NewFakeRecorder (10 )
3042+ d := & DependenciesDistributor {
3043+ Client : fake .NewClientBuilder ().WithScheme (Scheme ).WithObjects (exist , rb1 , rb2 ).Build (),
3044+ EventRecorder : fakeRec ,
3045+ }
3046+
3047+ if err := d .createOrUpdateAttachedBinding (attached ); err != nil {
3048+ t .Fatalf ("createOrUpdateAttachedBinding() error = %v" , err )
3049+ }
3050+
3051+ select {
3052+ case e := <- fakeRec .Events :
3053+ if ! strings .Contains (e , corev1 .EventTypeWarning ) {
3054+ t .Fatalf ("expected Warning event, got %q" , e )
3055+ }
3056+ if ! strings .Contains (e , events .EventReasonDependencyPolicyConflict ) {
3057+ t .Fatalf ("expected reason %q in event, got %q" , events .EventReasonDependencyPolicyConflict , e )
3058+ }
3059+ if ! strings .Contains (e , "ConflictResolution mismatch (Overwrite vs Abort)" ) {
3060+ t .Fatalf ("expected ConflictResolution mismatch hint in event, got %q" , e )
3061+ }
3062+ if ! strings .Contains (e , "PreserveResourcesOnDeletion mismatch (true vs false)" ) {
3063+ t .Fatalf ("expected PreserveResourcesOnDeletion mismatch hint in event, got %q" , e )
3064+ }
3065+ case <- time .After (1 * time .Second ):
3066+ t .Fatalf ("expected dependency policy conflict event, but none received" )
3067+ }
3068+ }
3069+
29653070func Test_buildAttachedBinding (t * testing.T ) {
29663071 blockOwnerDeletion := true
29673072 isController := true
0 commit comments