Merge pull request #3821 from chaosi-zju/mig-dev
feat: realization of ConflictResolution in PP
This commit is contained in:
commit
796a6caf62
|
@ -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.
|
||||
|
|
|
@ -27,6 +27,7 @@ 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)
|
||||
|
@ -34,12 +35,14 @@ func ensureWork(
|
|||
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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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(),
|
||||
|
@ -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(),
|
||||
|
|
|
@ -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 {
|
||||
|
@ -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 :
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue