Skip to content

Commit 7f5882e

Browse files
committed
Add DependencyPolicyConflict and DependencyPolicyAggregated event
1 parent f3c4376 commit 7f5882e

File tree

3 files changed

+190
-1
lines changed

3 files changed

+190
-1
lines changed

pkg/dependenciesdistributor/dependencies_distributor.go

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import (
2020
"context"
2121
"encoding/json"
2222
"fmt"
23+
"sort"
24+
"strings"
2325

2426
corev1 "k8s.io/api/core/v1"
2527
"k8s.io/apimachinery/pkg/api/equality"
@@ -49,6 +51,7 @@ import (
4951
"sigs.k8s.io/controller-runtime/pkg/source"
5052

5153
configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
54+
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
5255
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
5356
"github.com/karmada-io/karmada/pkg/events"
5457
"github.com/karmada-io/karmada/pkg/resourceinterpreter"
@@ -585,20 +588,59 @@ func (d *DependenciesDistributor) createOrUpdateAttachedBinding(attachedBinding
585588
"try again later after binding is garbage collected, see https://github.com/karmada-io/karmada/issues/6034", bindingKey)
586589
}
587590

591+
oldConflictResolution := effectiveConflictResolution(existBinding.Spec.ConflictResolution)
592+
oldPreserve := ptr.Deref(existBinding.Spec.PreserveResourcesOnDeletion, false)
593+
oldRequiredByLen := len(existBinding.Spec.RequiredBy)
594+
595+
mergedRequiredBy := mergeBindingSnapshot(existBinding.Spec.RequiredBy, attachedBinding.Spec.RequiredBy)
596+
parentsForEvent := formatBindingParents(mergedRequiredBy)
597+
multiParentAfter := len(mergedRequiredBy) > 1
598+
599+
finalConflictResolution := effectiveConflictResolution(attachedBinding.Spec.ConflictResolution)
600+
finalPreserve := ptr.Deref(attachedBinding.Spec.PreserveResourcesOnDeletion, false)
601+
602+
var conflictDetails []string
603+
conflictDetected := false
604+
shouldEmitAggregationEvent := false
605+
588606
// If the spec.Placement is nil, this means that existBinding is generated by the dependency mechanism.
589607
// If the spec.Placement is not nil, then it must be generated by PropagationPolicy.
590608
if existBinding.Spec.Placement == nil {
609+
if oldRequiredByLen > 0 && oldConflictResolution != finalConflictResolution {
610+
conflictDetected = true
611+
conflictDetails = append(conflictDetails, fmt.Sprintf("conflictResolution existing=%s incoming=%s",
612+
oldConflictResolution, finalConflictResolution))
613+
}
614+
if oldRequiredByLen > 0 && oldPreserve != finalPreserve {
615+
conflictDetected = true
616+
conflictDetails = append(conflictDetails, fmt.Sprintf("preserveResourcesOnDeletion existing=%t incoming=%t",
617+
oldPreserve, finalPreserve))
618+
}
591619
existBinding.Spec.ConflictResolution = attachedBinding.Spec.ConflictResolution
620+
shouldEmitAggregationEvent = multiParentAfter
592621
}
593-
existBinding.Spec.RequiredBy = mergeBindingSnapshot(existBinding.Spec.RequiredBy, attachedBinding.Spec.RequiredBy)
622+
623+
existBinding.Spec.RequiredBy = mergedRequiredBy
594624
existBinding.Labels = util.DedupeAndMergeLabels(existBinding.Labels, attachedBinding.Labels)
595625
existBinding.Spec.Resource = attachedBinding.Spec.Resource
596626
existBinding.Spec.PreserveResourcesOnDeletion = attachedBinding.Spec.PreserveResourcesOnDeletion
597627

628+
if conflictDetected {
629+
message := fmt.Sprintf("[dep-agg] conflict on %s/%s from parents %s: %s",
630+
existBinding.Namespace, existBinding.Name, strings.Join(parentsForEvent, ","), strings.Join(conflictDetails, "; "))
631+
d.EventRecorder.Eventf(existBinding, corev1.EventTypeWarning, events.EventReasonDependencyPolicyConflict, message)
632+
}
633+
598634
if err := d.Client.Update(context.TODO(), existBinding); err != nil {
599635
klog.Errorf("Failed to update resourceBinding(%s): %v", bindingKey, err)
600636
return err
601637
}
638+
639+
if shouldEmitAggregationEvent {
640+
message := fmt.Sprintf("[dep-agg] aggregated policy for %s/%s from parents %s: conflictResolution=%s preserveResourcesOnDeletion=%t",
641+
existBinding.Namespace, existBinding.Name, strings.Join(parentsForEvent, ","), finalConflictResolution, finalPreserve)
642+
d.EventRecorder.Eventf(existBinding, corev1.EventTypeNormal, events.EventReasonDependencyPolicyAggregated, message)
643+
}
602644
return nil
603645
}
604646

@@ -684,6 +726,32 @@ func (d *DependenciesDistributor) SetupWithManager(mgr controllerruntime.Manager
684726
})
685727
}
686728

729+
func effectiveConflictResolution(value policyv1alpha1.ConflictResolution) policyv1alpha1.ConflictResolution {
730+
if value == "" {
731+
return policyv1alpha1.ConflictAbort
732+
}
733+
734+
return value
735+
}
736+
737+
func formatBindingParents(snapshots []workv1alpha2.BindingSnapshot) []string {
738+
if len(snapshots) == 0 {
739+
return nil
740+
}
741+
742+
parents := make([]string, 0, len(snapshots))
743+
for _, snapshot := range snapshots {
744+
if snapshot.Namespace != "" {
745+
parents = append(parents, fmt.Sprintf("%s/%s", snapshot.Namespace, snapshot.Name))
746+
continue
747+
}
748+
parents = append(parents, snapshot.Name)
749+
}
750+
751+
sort.Strings(parents)
752+
return parents
753+
}
754+
687755
func generateBindingDependedLabels(bindingID, bindingNamespace, bindingName string) map[string]string {
688756
labelKey := generateBindingDependedLabelKey(bindingNamespace, bindingName)
689757
return map[string]string{labelKey: bindingID}

pkg/dependenciesdistributor/dependencies_distributor_test.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

pkg/events/events.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ const (
6666
EventReasonSyncScheduleResultToDependenciesSucceed = "SyncScheduleResultToDependenciesSucceed"
6767
// EventReasonSyncScheduleResultToDependenciesFailed indicates sync schedule result to attached bindings failed.
6868
EventReasonSyncScheduleResultToDependenciesFailed = "SyncScheduleResultToDependenciesFailed"
69+
// EventReasonDependencyPolicyConflict indicates conflict detected while aggregating dependency policies.
70+
EventReasonDependencyPolicyConflict = "DependencyPolicyConflict"
71+
// EventReasonDependencyPolicyAggregated indicates dependency policies aggregated successfully.
72+
EventReasonDependencyPolicyAggregated = "DependencyPolicyAggregated"
6973
)
7074

7175
// Define events for ResourceBinding, ClusterResourceBinding objects and their associated resources.

0 commit comments

Comments
 (0)