Skip to content

Commit 40cfc94

Browse files
committed
Add DependencyPolicyConflictResolved event
Signed-off-by: Kexin2000 <3299133282@qq.com>
1 parent f3c4376 commit 40cfc94

File tree

3 files changed

+170
-1
lines changed

3 files changed

+170
-1
lines changed

pkg/dependenciesdistributor/dependencies_distributor.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import (
4949
"sigs.k8s.io/controller-runtime/pkg/source"
5050

5151
configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
52+
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
5253
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
5354
"github.com/karmada-io/karmada/pkg/events"
5455
"github.com/karmada-io/karmada/pkg/resourceinterpreter"
@@ -585,16 +586,37 @@ func (d *DependenciesDistributor) createOrUpdateAttachedBinding(attachedBinding
585586
"try again later after binding is garbage collected, see https://github.com/karmada-io/karmada/issues/6034", bindingKey)
586587
}
587588

589+
oldConflictResolution := effectiveConflictResolution(existBinding.Spec.ConflictResolution)
590+
oldPreserve := ptr.Deref(existBinding.Spec.PreserveResourcesOnDeletion, false)
591+
oldRequiredByLen := len(existBinding.Spec.RequiredBy)
592+
593+
mergedRequiredBy := mergeBindingSnapshot(existBinding.Spec.RequiredBy, attachedBinding.Spec.RequiredBy)
594+
595+
newConflictResolution := effectiveConflictResolution(attachedBinding.Spec.ConflictResolution)
596+
newPreserve := ptr.Deref(attachedBinding.Spec.PreserveResourcesOnDeletion, false)
597+
598+
conflictDetected := false
599+
588600
// If the spec.Placement is nil, this means that existBinding is generated by the dependency mechanism.
589601
// If the spec.Placement is not nil, then it must be generated by PropagationPolicy.
590602
if existBinding.Spec.Placement == nil {
603+
if oldRequiredByLen > 0 && (oldConflictResolution != newConflictResolution || oldPreserve != newPreserve) {
604+
conflictDetected = true
605+
}
591606
existBinding.Spec.ConflictResolution = attachedBinding.Spec.ConflictResolution
592607
}
593-
existBinding.Spec.RequiredBy = mergeBindingSnapshot(existBinding.Spec.RequiredBy, attachedBinding.Spec.RequiredBy)
608+
609+
existBinding.Spec.RequiredBy = mergedRequiredBy
594610
existBinding.Labels = util.DedupeAndMergeLabels(existBinding.Labels, attachedBinding.Labels)
595611
existBinding.Spec.Resource = attachedBinding.Spec.Resource
596612
existBinding.Spec.PreserveResourcesOnDeletion = attachedBinding.Spec.PreserveResourcesOnDeletion
597613

614+
if conflictDetected && d.EventRecorder != nil {
615+
// Emit a warning event indicating conflict was detected and auto-resolved by default rules.
616+
message := "Dependency policy conflict detected and resolved by default rules."
617+
d.EventRecorder.Eventf(existBinding, corev1.EventTypeWarning, events.EventReasonDependencyPolicyConflictResolved, message)
618+
}
619+
598620
if err := d.Client.Update(context.TODO(), existBinding); err != nil {
599621
klog.Errorf("Failed to update resourceBinding(%s): %v", bindingKey, err)
600622
return err
@@ -684,6 +706,14 @@ func (d *DependenciesDistributor) SetupWithManager(mgr controllerruntime.Manager
684706
})
685707
}
686708

709+
func effectiveConflictResolution(value policyv1alpha1.ConflictResolution) policyv1alpha1.ConflictResolution {
710+
if value == "" {
711+
return policyv1alpha1.ConflictAbort
712+
}
713+
714+
return value
715+
}
716+
687717
func generateBindingDependedLabels(bindingID, bindingNamespace, bindingName string) map[string]string {
688718
labelKey := generateBindingDependedLabelKey(bindingNamespace, bindingName)
689719
return map[string]string{labelKey: bindingID}

pkg/dependenciesdistributor/dependencies_distributor_test.go

Lines changed: 136 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,136 @@ func Test_deleteBindingFromSnapshot(t *testing.T) {
34323435
})
34333436
}
34343437
}
3438+
3439+
type dependencyPolicyTestEnv struct {
3440+
distributor *DependenciesDistributor
3441+
client client.Client
3442+
recorder *record.FakeRecorder
3443+
existing *workv1alpha2.ResourceBinding
3444+
dependent *unstructured.Unstructured
3445+
}
3446+
3447+
func newDependencyPolicyTestEnv(t *testing.T) *dependencyPolicyTestEnv {
3448+
t.Helper()
3449+
3450+
scheme := runtime.NewScheme()
3451+
utilruntime.Must(workv1alpha2.Install(scheme))
3452+
3453+
dependent := &unstructured.Unstructured{}
3454+
dependent.SetAPIVersion("v1")
3455+
dependent.SetKind("ConfigMap")
3456+
dependent.SetNamespace("default")
3457+
dependent.SetName("app-config")
3458+
dependent.SetUID(types.UID("cm-uid"))
3459+
3460+
parentOne := &workv1alpha2.ResourceBinding{
3461+
ObjectMeta: metav1.ObjectMeta{
3462+
Name: "workload-a",
3463+
Namespace: "default",
3464+
Labels: map[string]string{
3465+
workv1alpha2.ResourceBindingPermanentIDLabel: "rb-uid-a",
3466+
},
3467+
},
3468+
Spec: workv1alpha2.ResourceBindingSpec{
3469+
Clusters: []workv1alpha2.TargetCluster{{Name: "member1"}},
3470+
ConflictResolution: policyv1alpha1.ConflictOverwrite,
3471+
PreserveResourcesOnDeletion: ptr.To(true),
3472+
},
3473+
}
3474+
3475+
attachedFromParentOne := buildAttachedBinding(parentOne, dependent)
3476+
existing := attachedFromParentOne.DeepCopy()
3477+
3478+
fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(existing).Build()
3479+
recorder := record.NewFakeRecorder(10)
3480+
3481+
distributor := &DependenciesDistributor{
3482+
Client: fakeClient,
3483+
EventRecorder: recorder,
3484+
}
3485+
3486+
return &dependencyPolicyTestEnv{
3487+
distributor: distributor,
3488+
client: fakeClient,
3489+
recorder: recorder,
3490+
existing: existing,
3491+
dependent: dependent,
3492+
}
3493+
}
3494+
3495+
func waitForEvents(t *testing.T, recorder *record.FakeRecorder, want int, timeout time.Duration) []string {
3496+
t.Helper()
3497+
3498+
eventList := make([]string, 0, want)
3499+
timeoutCh := time.After(timeout)
3500+
for len(eventList) < want {
3501+
select {
3502+
case evt := <-recorder.Events:
3503+
eventList = append(eventList, evt)
3504+
case <-timeoutCh:
3505+
t.Fatalf("timed out waiting for %d events, got %v", want, eventList)
3506+
}
3507+
}
3508+
return eventList
3509+
}
3510+
3511+
func findEventByReason(t *testing.T, eventList []string, reason string) string {
3512+
t.Helper()
3513+
3514+
for _, evt := range eventList {
3515+
if strings.Contains(evt, reason) {
3516+
return evt
3517+
}
3518+
}
3519+
t.Fatalf("expected event reason %s in %v", reason, eventList)
3520+
return ""
3521+
}
3522+
3523+
func assertEventMessageContains(t *testing.T, event string, substrings ...string) {
3524+
t.Helper()
3525+
3526+
for _, substring := range substrings {
3527+
if !strings.Contains(event, substring) {
3528+
t.Errorf("event %q missing substring %q", event, substring)
3529+
}
3530+
}
3531+
}
3532+
3533+
func TestCreateOrUpdateAttachedBinding_RecordsConflictEvent(t *testing.T) {
3534+
env := newDependencyPolicyTestEnv(t)
3535+
3536+
parentTwo := &workv1alpha2.ResourceBinding{
3537+
ObjectMeta: metav1.ObjectMeta{
3538+
Name: "workload-b",
3539+
Namespace: "default",
3540+
Labels: map[string]string{
3541+
workv1alpha2.ResourceBindingPermanentIDLabel: "rb-uid-b",
3542+
},
3543+
},
3544+
Spec: workv1alpha2.ResourceBindingSpec{
3545+
Clusters: []workv1alpha2.TargetCluster{{Name: "member2"}},
3546+
ConflictResolution: policyv1alpha1.ConflictAbort,
3547+
PreserveResourcesOnDeletion: ptr.To(false),
3548+
},
3549+
}
3550+
attachedFromParentTwo := buildAttachedBinding(parentTwo, env.dependent)
3551+
3552+
if err := env.distributor.createOrUpdateAttachedBinding(attachedFromParentTwo); err != nil {
3553+
t.Fatalf("createOrUpdateAttachedBinding() error = %v", err)
3554+
}
3555+
3556+
updated := &workv1alpha2.ResourceBinding{}
3557+
if err := env.client.Get(context.TODO(), client.ObjectKeyFromObject(env.existing), updated); err != nil {
3558+
t.Fatalf("failed to fetch updated binding: %v", err)
3559+
}
3560+
if len(updated.Spec.RequiredBy) != 2 {
3561+
t.Fatalf("expected 2 parents after aggregation, got %d", len(updated.Spec.RequiredBy))
3562+
}
3563+
3564+
eventList := waitForEvents(t, env.recorder, 1, time.Second)
3565+
3566+
conflictEvent := findEventByReason(t, eventList, events.EventReasonDependencyPolicyConflictResolved)
3567+
assertEventMessageContains(t, conflictEvent,
3568+
"Dependency policy conflict detected and resolved by default rules.",
3569+
)
3570+
}

pkg/events/events.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ const (
6666
EventReasonSyncScheduleResultToDependenciesSucceed = "SyncScheduleResultToDependenciesSucceed"
6767
// EventReasonSyncScheduleResultToDependenciesFailed indicates sync schedule result to attached bindings failed.
6868
EventReasonSyncScheduleResultToDependenciesFailed = "SyncScheduleResultToDependenciesFailed"
69+
// EventReasonDependencyPolicyConflictResolved indicates a dependency policy conflict was detected
70+
// and has been resolved automatically by default rules.
71+
EventReasonDependencyPolicyConflictResolved = "DependencyPolicyConflictResolved"
6972
)
7073

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

0 commit comments

Comments
 (0)