@@ -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"
@@ -3432,3 +3435,117 @@ func Test_deleteBindingFromSnapshot(t *testing.T) {
34323435 })
34333436 }
34343437}
3438+
3439+ func TestCreateOrUpdateAttachedBinding_EmitsDependencyEvents (t * testing.T ) {
3440+ testScheme := runtime .NewScheme ()
3441+ utilruntime .Must (workv1alpha2 .AddToScheme (testScheme ))
3442+
3443+ dependentObj := & unstructured.Unstructured {}
3444+ dependentObj .SetAPIVersion ("v1" )
3445+ dependentObj .SetKind ("ConfigMap" )
3446+ dependentObj .SetNamespace ("default" )
3447+ dependentObj .SetName ("app-config" )
3448+ dependentObj .SetUID (types .UID ("cm-uid" ))
3449+
3450+ parentOne := & workv1alpha2.ResourceBinding {
3451+ ObjectMeta : metav1.ObjectMeta {
3452+ Name : "workload-a" ,
3453+ Namespace : "default" ,
3454+ Labels : map [string ]string {
3455+ workv1alpha2 .ResourceBindingPermanentIDLabel : "rb-uid-a" ,
3456+ },
3457+ },
3458+ Spec : workv1alpha2.ResourceBindingSpec {
3459+ Clusters : []workv1alpha2.TargetCluster {{Name : "member1" }},
3460+ ConflictResolution : policyv1alpha1 .ConflictOverwrite ,
3461+ PreserveResourcesOnDeletion : ptr .To (true ),
3462+ },
3463+ }
3464+ attachedFromParentOne := buildAttachedBinding (parentOne , dependentObj )
3465+ // Simulate existing dependency binding already persisted in the cluster.
3466+ existing := attachedFromParentOne .DeepCopy ()
3467+
3468+ fakeClient := fake .NewClientBuilder ().WithScheme (testScheme ).WithObjects (existing ).Build ()
3469+ recorder := record .NewFakeRecorder (10 )
3470+ d := & DependenciesDistributor {
3471+ Client : fakeClient ,
3472+ EventRecorder : recorder ,
3473+ }
3474+
3475+ parentTwo := & workv1alpha2.ResourceBinding {
3476+ ObjectMeta : metav1.ObjectMeta {
3477+ Name : "workload-b" ,
3478+ Namespace : "default" ,
3479+ Labels : map [string ]string {
3480+ workv1alpha2 .ResourceBindingPermanentIDLabel : "rb-uid-b" ,
3481+ },
3482+ },
3483+ Spec : workv1alpha2.ResourceBindingSpec {
3484+ Clusters : []workv1alpha2.TargetCluster {{Name : "member2" }},
3485+ ConflictResolution : policyv1alpha1 .ConflictAbort ,
3486+ PreserveResourcesOnDeletion : ptr .To (false ),
3487+ },
3488+ }
3489+ attachedFromParentTwo := buildAttachedBinding (parentTwo , dependentObj )
3490+
3491+ if err := d .createOrUpdateAttachedBinding (attachedFromParentTwo ); err != nil {
3492+ t .Fatalf ("createOrUpdateAttachedBinding() error = %v" , err )
3493+ }
3494+
3495+ updated := & workv1alpha2.ResourceBinding {}
3496+ if err := fakeClient .Get (context .TODO (), client .ObjectKeyFromObject (existing ), updated ); err != nil {
3497+ t .Fatalf ("failed to fetch updated binding: %v" , err )
3498+ }
3499+ if len (updated .Spec .RequiredBy ) != 2 {
3500+ t .Fatalf ("expected 2 parents after aggregation, got %d" , len (updated .Spec .RequiredBy ))
3501+ }
3502+
3503+ eventsReceived := make ([]string , 0 , 2 )
3504+ for len (eventsReceived ) < 2 {
3505+ select {
3506+ case evt := <- recorder .Events :
3507+ eventsReceived = append (eventsReceived , evt )
3508+ case <- time .After (time .Second ):
3509+ t .Fatalf ("timed out waiting for dependency aggregation events, got %v" , eventsReceived )
3510+ }
3511+ }
3512+
3513+ var conflictEvent , aggregatedEvent string
3514+ for _ , evt := range eventsReceived {
3515+ if strings .Contains (evt , events .EventReasonDependencyPolicyConflict ) {
3516+ conflictEvent = evt
3517+ }
3518+ if strings .Contains (evt , events .EventReasonDependencyPolicyAggregated ) {
3519+ aggregatedEvent = evt
3520+ }
3521+ }
3522+
3523+ if conflictEvent == "" {
3524+ t .Fatalf ("expected conflict event in %v" , eventsReceived )
3525+ }
3526+ if ! strings .Contains (conflictEvent , "[dep-agg] conflict" ) {
3527+ t .Errorf ("unexpected conflict event message: %s" , conflictEvent )
3528+ }
3529+ if ! strings .Contains (conflictEvent , "conflictResolution existing=Overwrite incoming=Abort" ) {
3530+ t .Errorf ("conflict event missing conflictResolution details: %s" , conflictEvent )
3531+ }
3532+ if ! strings .Contains (conflictEvent , "preserveResourcesOnDeletion existing=true incoming=false" ) {
3533+ t .Errorf ("conflict event missing preserveResourcesOnDeletion details: %s" , conflictEvent )
3534+ }
3535+
3536+ if aggregatedEvent == "" {
3537+ t .Fatalf ("expected aggregation event in %v" , eventsReceived )
3538+ }
3539+ if ! strings .Contains (aggregatedEvent , "[dep-agg] aggregated policy" ) {
3540+ t .Errorf ("unexpected aggregation event message: %s" , aggregatedEvent )
3541+ }
3542+ if ! strings .Contains (aggregatedEvent , "conflictResolution=Abort" ) {
3543+ t .Errorf ("aggregation event missing conflictResolution result: %s" , aggregatedEvent )
3544+ }
3545+ if ! strings .Contains (aggregatedEvent , "preserveResourcesOnDeletion=false" ) {
3546+ t .Errorf ("aggregation event missing preserveResourcesOnDeletion result: %s" , aggregatedEvent )
3547+ }
3548+ if ! strings .Contains (aggregatedEvent , "default/workload-a" ) || ! strings .Contains (aggregatedEvent , "default/workload-b" ) {
3549+ t .Errorf ("aggregation event missing parent list: %s" , aggregatedEvent )
3550+ }
3551+ }
0 commit comments