Skip to content

Commit

Permalink
Merge pull request #3821 from chaosi-zju/mig-dev
Browse files Browse the repository at this point in the history
feat: realization of ConflictResolution in PP
  • Loading branch information
karmada-bot authored Jul 26, 2023
2 parents 683af57 + 2b060cb commit 796a6ca
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 10 deletions.
6 changes: 5 additions & 1 deletion pkg/apis/work/v1alpha2/well_known_constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,15 @@ const (
// The valid value is:
// - overwrite: always overwrite the resource if already exist. The resource will be overwritten with the
// configuration from control plane.
// - abort: do not resolve the conflict and stop propagating to avoid unexpected overwrites (default value)
// Note: Propagation of the resource template without this annotation will fail in case of already exists.
ResourceConflictResolutionAnnotation = "work.karmada.io/conflict-resolution"

// ResourceConflictResolutionOverwrite is the value of ResourceConflictResolutionAnnotation, indicating the overwrite strategy.
// ResourceConflictResolutionOverwrite is a value of ResourceConflictResolutionAnnotation, indicating the overwrite strategy.
ResourceConflictResolutionOverwrite = "overwrite"

// ResourceConflictResolutionAbort is a value of ResourceConflictResolutionAnnotation, indicating stop propagating.
ResourceConflictResolutionAbort = "abort"
)

// Define annotations that are added to the resource template.
Expand Down
30 changes: 30 additions & 0 deletions pkg/controllers/binding/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,22 @@ func ensureWork(
var placement *policyv1alpha1.Placement
var requiredByBindingSnapshot []workv1alpha2.BindingSnapshot
var replicas int32
var conflictResolutionInBinding policyv1alpha1.ConflictResolution
switch scope {
case apiextensionsv1.NamespaceScoped:
bindingObj := binding.(*workv1alpha2.ResourceBinding)
targetClusters = bindingObj.Spec.Clusters
requiredByBindingSnapshot = bindingObj.Spec.RequiredBy
placement = bindingObj.Spec.Placement
replicas = bindingObj.Spec.Replicas
conflictResolutionInBinding = bindingObj.Spec.ConflictResolution
case apiextensionsv1.ClusterScoped:
bindingObj := binding.(*workv1alpha2.ClusterResourceBinding)
targetClusters = bindingObj.Spec.Clusters
requiredByBindingSnapshot = bindingObj.Spec.RequiredBy
placement = bindingObj.Spec.Placement
replicas = bindingObj.Spec.Replicas
conflictResolutionInBinding = bindingObj.Spec.ConflictResolution
}

targetClusters = mergeTargetClusters(targetClusters, requiredByBindingSnapshot)
Expand Down Expand Up @@ -93,6 +96,7 @@ func ensureWork(
workLabel := mergeLabel(clonedWorkload, workNamespace, binding, scope)

annotations := mergeAnnotations(clonedWorkload, binding, scope)
annotations = mergeConflictResolution(clonedWorkload, conflictResolutionInBinding, annotations)
annotations, err = RecordAppliedOverrides(cops, ops, annotations)
if err != nil {
klog.Errorf("Failed to record appliedOverrides, Error: %v", err)
Expand Down Expand Up @@ -193,6 +197,32 @@ func RecordAppliedOverrides(cops *overridemanager.AppliedOverrides, ops *overrid
return annotations, nil
}

// mergeConflictResolution determine the conflictResolution annotation of Work: preferentially inherit from RT, then RB
func mergeConflictResolution(workload *unstructured.Unstructured, conflictResolutionInBinding policyv1alpha1.ConflictResolution,
annotations map[string]string) map[string]string {
// conflictResolutionInRT refer to the annotation in ResourceTemplate
conflictResolutionInRT := util.GetAnnotationValue(workload.GetAnnotations(), workv1alpha2.ResourceConflictResolutionAnnotation)

// the final conflictResolution annotation value of Work inherit from RT preferentially
// so if conflictResolution annotation is defined in RT already, just copy the value and return
if conflictResolutionInRT == workv1alpha2.ResourceConflictResolutionOverwrite || conflictResolutionInRT == workv1alpha2.ResourceConflictResolutionAbort {
annotations[workv1alpha2.ResourceConflictResolutionAnnotation] = conflictResolutionInRT
return annotations
} else if conflictResolutionInRT != "" {
// ignore its value and add logs if conflictResolutionInRT is neither abort nor overwrite.
klog.Warningf("ignore the invalid conflict-resolution annotation in ResourceTemplate %s/%s/%s: %s",
workload.GetKind(), workload.GetNamespace(), workload.GetName(), conflictResolutionInRT)
}

if conflictResolutionInBinding == policyv1alpha1.ConflictOverwrite {
annotations[workv1alpha2.ResourceConflictResolutionAnnotation] = workv1alpha2.ResourceConflictResolutionOverwrite
return annotations
}

annotations[workv1alpha2.ResourceConflictResolutionAnnotation] = workv1alpha2.ResourceConflictResolutionAbort
return annotations
}

func divideReplicasByJobCompletions(workload *unstructured.Unstructured, clusters []workv1alpha2.TargetCluster) ([]workv1alpha2.TargetCluster, error) {
var targetClusters []workv1alpha2.TargetCluster
completions, found, err := unstructured.NestedInt64(workload.Object, util.SpecField, util.CompletionsField)
Expand Down
72 changes: 72 additions & 0 deletions pkg/controllers/binding/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
"github.com/karmada-io/karmada/pkg/util/names"
)
Expand Down Expand Up @@ -215,3 +216,74 @@ func Test_mergeAnnotations(t *testing.T) {
})
}
}

func Test_mergeConflictResolution(t *testing.T) {
namespace := "fake-ns"
workload := unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "test-deployment",
"namespace": namespace,
},
},
}
workloadOverwrite := workload.DeepCopy()
workloadOverwrite.SetAnnotations(map[string]string{workv1alpha2.ResourceConflictResolutionAnnotation: workv1alpha2.ResourceConflictResolutionOverwrite})
workloadAbort := workload.DeepCopy()
workloadAbort.SetAnnotations(map[string]string{workv1alpha2.ResourceConflictResolutionAnnotation: workv1alpha2.ResourceConflictResolutionAbort})
workloadInvalid := workload.DeepCopy()
workloadInvalid.SetAnnotations(map[string]string{workv1alpha2.ResourceConflictResolutionAnnotation: "unknown"})

tests := []struct {
name string
workload *unstructured.Unstructured
conflictResolutionInBinding policyv1alpha1.ConflictResolution
annotations map[string]string
want map[string]string
}{
{
name: "EmptyInRT_OverwriteInRB",
workload: &workload,
conflictResolutionInBinding: policyv1alpha1.ConflictOverwrite,
annotations: map[string]string{},
want: map[string]string{workv1alpha2.ResourceConflictResolutionAnnotation: workv1alpha2.ResourceConflictResolutionOverwrite},
},
{
name: "EmptyInRT_AbortInRB",
workload: &workload,
conflictResolutionInBinding: policyv1alpha1.ConflictAbort,
annotations: map[string]string{},
want: map[string]string{workv1alpha2.ResourceConflictResolutionAnnotation: workv1alpha2.ResourceConflictResolutionAbort},
},
{
name: "OverwriteInRT_AbortInPP",
workload: workloadOverwrite,
conflictResolutionInBinding: policyv1alpha1.ConflictAbort,
annotations: map[string]string{},
want: map[string]string{workv1alpha2.ResourceConflictResolutionAnnotation: workv1alpha2.ResourceConflictResolutionOverwrite},
},
{
name: "AbortInRT_OverwriteInPP",
workload: workloadAbort,
conflictResolutionInBinding: policyv1alpha1.ConflictOverwrite,
annotations: map[string]string{},
want: map[string]string{workv1alpha2.ResourceConflictResolutionAnnotation: workv1alpha2.ResourceConflictResolutionAbort},
},
{
name: "InvalidInRT_OverwriteInPP",
workload: workloadInvalid,
conflictResolutionInBinding: policyv1alpha1.ConflictOverwrite,
annotations: map[string]string{},
want: map[string]string{workv1alpha2.ResourceConflictResolutionAnnotation: workv1alpha2.ResourceConflictResolutionOverwrite},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := mergeConflictResolution(tt.workload, tt.conflictResolutionInBinding, tt.annotations); !reflect.DeepEqual(got, tt.want) {
t.Errorf("mergeConflictResolution() = %v, want %v", got, tt.want)
}
})
}
}
21 changes: 13 additions & 8 deletions pkg/detector/detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ func (d *ResourceDetector) ApplyPolicy(object *unstructured.Unstructured, object
bindingCopy.Spec.SchedulerName = binding.Spec.SchedulerName
bindingCopy.Spec.Placement = binding.Spec.Placement
bindingCopy.Spec.Failover = binding.Spec.Failover
bindingCopy.Spec.ConflictResolution = binding.Spec.ConflictResolution
return nil
})
if err != nil {
Expand Down Expand Up @@ -497,6 +498,7 @@ func (d *ResourceDetector) ApplyClusterPolicy(object *unstructured.Unstructured,
bindingCopy.Spec.SchedulerName = binding.Spec.SchedulerName
bindingCopy.Spec.Placement = binding.Spec.Placement
bindingCopy.Spec.Failover = binding.Spec.Failover
bindingCopy.Spec.ConflictResolution = binding.Spec.ConflictResolution
return nil
})
if err != nil {
Expand Down Expand Up @@ -541,6 +543,7 @@ func (d *ResourceDetector) ApplyClusterPolicy(object *unstructured.Unstructured,
bindingCopy.Spec.SchedulerName = binding.Spec.SchedulerName
bindingCopy.Spec.Placement = binding.Spec.Placement
bindingCopy.Spec.Failover = binding.Spec.Failover
bindingCopy.Spec.ConflictResolution = binding.Spec.ConflictResolution
return nil
})
if err != nil {
Expand Down Expand Up @@ -636,10 +639,11 @@ func (d *ResourceDetector) BuildResourceBinding(object *unstructured.Unstructure
Finalizers: []string{util.BindingControllerFinalizer},
},
Spec: workv1alpha2.ResourceBindingSpec{
PropagateDeps: policySpec.PropagateDeps,
SchedulerName: policySpec.SchedulerName,
Placement: &policySpec.Placement,
Failover: policySpec.Failover,
PropagateDeps: policySpec.PropagateDeps,
SchedulerName: policySpec.SchedulerName,
Placement: &policySpec.Placement,
Failover: policySpec.Failover,
ConflictResolution: policySpec.ConflictResolution,
Resource: workv1alpha2.ObjectReference{
APIVersion: object.GetAPIVersion(),
Kind: object.GetKind(),
Expand Down Expand Up @@ -677,10 +681,11 @@ func (d *ResourceDetector) BuildClusterResourceBinding(object *unstructured.Unst
Finalizers: []string{util.ClusterResourceBindingControllerFinalizer},
},
Spec: workv1alpha2.ResourceBindingSpec{
PropagateDeps: policySpec.PropagateDeps,
SchedulerName: policySpec.SchedulerName,
Placement: &policySpec.Placement,
Failover: policySpec.Failover,
PropagateDeps: policySpec.PropagateDeps,
SchedulerName: policySpec.SchedulerName,
Placement: &policySpec.Placement,
Failover: policySpec.Failover,
ConflictResolution: policySpec.ConflictResolution,
Resource: workv1alpha2.ObjectReference{
APIVersion: object.GetAPIVersion(),
Kind: object.GetKind(),
Expand Down
13 changes: 12 additions & 1 deletion pkg/util/annotation.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
)

// MergeAnnotation adds annotation for the given object.
// MergeAnnotation adds annotation for the given object, keep the value unchanged if key exist.
func MergeAnnotation(obj *unstructured.Unstructured, annotationKey string, annotationValue string) {
objectAnnotation := obj.GetAnnotations()
if objectAnnotation == nil {
Expand All @@ -23,6 +23,17 @@ func MergeAnnotation(obj *unstructured.Unstructured, annotationKey string, annot
}
}

// ReplaceAnnotation adds annotation for the given object, replace the value if key exist.
func ReplaceAnnotation(obj *unstructured.Unstructured, annotationKey string, annotationValue string) {
objectAnnotation := obj.GetAnnotations()
if objectAnnotation == nil {
objectAnnotation = make(map[string]string, 1)
}

objectAnnotation[annotationKey] = annotationValue
obj.SetAnnotations(objectAnnotation)
}

// RetainAnnotations merges the annotations that added by controllers running
// in member cluster to avoid overwriting.
// Following keys will be ignored if :
Expand Down
54 changes: 54 additions & 0 deletions pkg/util/annotation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -467,3 +467,57 @@ func TestRecordManagedAnnotations(t *testing.T) {
})
}
}

func TestReplaceAnnotation(t *testing.T) {
workload := unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "demo-deployment",
},
},
}
workloadExistKey := workload.DeepCopy()
workloadExistKey.SetAnnotations(map[string]string{"testKey": "oldValue"})
workloadNotExistKey := workload.DeepCopy()
workloadNotExistKey.SetAnnotations(map[string]string{"anotherKey": "anotherValue"})

tests := []struct {
name string
obj *unstructured.Unstructured
annotationKey string
annotationValue string
want map[string]string
}{
{
name: "nil annotation",
obj: &workload,
annotationKey: "testKey",
annotationValue: "newValue",
want: map[string]string{"testKey": "newValue"},
},
{
name: "exist key",
obj: workloadExistKey,
annotationKey: "testKey",
annotationValue: "newValue",
want: map[string]string{"testKey": "newValue"},
},
{
name: "not exist key",
obj: workloadNotExistKey,
annotationKey: "testKey",
annotationValue: "newValue",
want: map[string]string{"anotherKey": "anotherValue", "testKey": "newValue"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ReplaceAnnotation(tt.obj, tt.annotationKey, tt.annotationValue)
if !reflect.DeepEqual(tt.obj.GetAnnotations(), tt.want) {
t.Errorf("ReplaceAnnotation(), obj.GetAnnotations = %v, want %v", tt.obj.GetAnnotations(), tt.want)
}
})
}
}
3 changes: 3 additions & 0 deletions pkg/util/helper/work.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import (
// CreateOrUpdateWork creates a Work object if not exist, or updates if it already exist.
func CreateOrUpdateWork(client client.Client, workMeta metav1.ObjectMeta, resource *unstructured.Unstructured) error {
workload := resource.DeepCopy()
if conflictResolution, ok := workMeta.GetAnnotations()[workv1alpha2.ResourceConflictResolutionAnnotation]; ok {
util.ReplaceAnnotation(workload, workv1alpha2.ResourceConflictResolutionAnnotation, conflictResolution)
}
util.MergeAnnotation(workload, workv1alpha2.ResourceTemplateUIDAnnotation, string(workload.GetUID()))
util.RecordManagedAnnotations(workload)
util.RecordManagedLabels(workload)
Expand Down

0 comments on commit 796a6ca

Please sign in to comment.