Reduce the number of reconciliations on update in detector

Signed-off-by: z50023861 <zhuwentao15@huawei.com>
This commit is contained in:
z50023861 2022-07-21 08:47:33 +08:00
parent 6969a5d643
commit 4469d39aa5
3 changed files with 245 additions and 0 deletions

View File

@ -320,6 +320,23 @@ func (d *ResourceDetector) OnAdd(obj interface{}) {
// OnUpdate handles object update event and push the object to queue.
func (d *ResourceDetector) OnUpdate(oldObj, newObj interface{}) {
unstructuredOldObj, err := helper.ToUnstructured(oldObj)
if err != nil {
klog.Errorf("Failed to transform oldObj, error: %v", err)
return
}
unstructuredNewObj, err := helper.ToUnstructured(newObj)
if err != nil {
klog.Errorf("Failed to transform newObj, error: %v", err)
return
}
if !SpecificationChanged(unstructuredOldObj, unstructuredNewObj) {
klog.V(4).Infof("Ignore update event of object (kind=%s, %s/%s) as specification no change", unstructuredOldObj.GetKind(), unstructuredOldObj.GetNamespace(), unstructuredOldObj.GetName())
return
}
d.OnAdd(newObj)
}

View File

@ -0,0 +1,23 @@
package detector
import (
"reflect"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// SpecificationChanged check if the specification of the given object change or not
func SpecificationChanged(oldObj, newObj *unstructured.Unstructured) bool {
oldBackup := oldObj.DeepCopy()
newBackup := newObj.DeepCopy()
// Remove the status and some system defined mutable fields in metadata, including managedFields and resourceVersion.
// Refer to https://kubernetes.io/docs/reference/kubernetes-api/common-definitions/object-meta/#ObjectMeta for more details.
removedFields := [][]string{{"status"}, {"metadata", "managedFields"}, {"metadata", "resourceVersion"}}
for _, r := range removedFields {
unstructured.RemoveNestedField(oldBackup.Object, r...)
unstructured.RemoveNestedField(newBackup.Object, r...)
}
return !reflect.DeepEqual(oldBackup, newBackup)
}

View File

@ -0,0 +1,205 @@
package detector
import (
"testing"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog/v2"
"k8s.io/utils/pointer"
workloadv1alpha1 "github.com/karmada-io/karmada/examples/customresourceinterpreter/apis/workload/v1alpha1"
"github.com/karmada-io/karmada/pkg/util/helper"
)
func TestSpecificationChanged(t *testing.T) {
tests := []struct {
name string
oldObj interface{}
newObj interface{}
wantChange bool
}{
{
name: "Only user defined fields changed(Deployment)",
oldObj: &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "123456",
ManagedFields: []metav1.ManagedFieldsEntry{},
},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"app": "nginx_v1"},
},
},
Status: appsv1.DeploymentStatus{},
},
newObj: &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "123456",
ManagedFields: []metav1.ManagedFieldsEntry{},
},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"app": "nginx_v2"},
},
},
Status: appsv1.DeploymentStatus{},
},
wantChange: true,
},
{
name: "Only status fields changed(Deployment)",
oldObj: &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "123456",
ManagedFields: []metav1.ManagedFieldsEntry{{}},
},
Spec: appsv1.DeploymentSpec{},
Status: appsv1.DeploymentStatus{Replicas: 3, UpdatedReplicas: 3, ReadyReplicas: 0},
},
newObj: &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "123498",
ManagedFields: []metav1.ManagedFieldsEntry{{}, {}},
},
Spec: appsv1.DeploymentSpec{},
Status: appsv1.DeploymentStatus{Replicas: 3, UpdatedReplicas: 1, ReadyReplicas: 2},
},
wantChange: false,
},
{
name: "Both user defined fields and status fields changed(Deployment)",
oldObj: &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "123456",
ManagedFields: []metav1.ManagedFieldsEntry{{}},
},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"app": "nginx_v1"},
},
},
Status: appsv1.DeploymentStatus{Replicas: 3, UpdatedReplicas: 3, ReadyReplicas: 0},
},
newObj: &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "123498",
ManagedFields: []metav1.ManagedFieldsEntry{{}, {}},
},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"app": "nginx_v2"},
},
},
Status: appsv1.DeploymentStatus{Replicas: 3, UpdatedReplicas: 1, ReadyReplicas: 2},
},
wantChange: true,
},
{
name: "Only user defined fields changed(CRD_WorkLoad)",
oldObj: &workloadv1alpha1.Workload{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "123456",
ManagedFields: []metav1.ManagedFieldsEntry{{}},
},
Spec: workloadv1alpha1.WorkloadSpec{
Replicas: pointer.Int32(3),
Template: v1.PodTemplateSpec{},
Paused: false,
},
Status: workloadv1alpha1.WorkloadStatus{ReadyReplicas: 3},
},
newObj: &workloadv1alpha1.Workload{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "123456",
ManagedFields: []metav1.ManagedFieldsEntry{{}},
},
Spec: workloadv1alpha1.WorkloadSpec{
Replicas: pointer.Int32(5),
Template: v1.PodTemplateSpec{},
Paused: false,
},
Status: workloadv1alpha1.WorkloadStatus{ReadyReplicas: 3},
},
wantChange: true,
},
{
name: "Only status fields changed(CRD_WorkLoad)",
oldObj: &workloadv1alpha1.Workload{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "123456",
ManagedFields: []metav1.ManagedFieldsEntry{{}},
},
Spec: workloadv1alpha1.WorkloadSpec{
Replicas: pointer.Int32(3),
Template: v1.PodTemplateSpec{},
Paused: false,
},
Status: workloadv1alpha1.WorkloadStatus{ReadyReplicas: 1},
},
newObj: &workloadv1alpha1.Workload{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "123498",
ManagedFields: []metav1.ManagedFieldsEntry{{}, {}},
},
Spec: workloadv1alpha1.WorkloadSpec{
Replicas: pointer.Int32(3),
Template: v1.PodTemplateSpec{},
Paused: false,
},
Status: workloadv1alpha1.WorkloadStatus{ReadyReplicas: 3},
},
wantChange: false,
},
{
name: "Both user defined fields and status fields changed(CRD_WorkLoad)",
oldObj: &workloadv1alpha1.Workload{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "123456",
ManagedFields: []metav1.ManagedFieldsEntry{{}},
},
Spec: workloadv1alpha1.WorkloadSpec{
Replicas: pointer.Int32(3),
Template: v1.PodTemplateSpec{},
Paused: false,
},
Status: workloadv1alpha1.WorkloadStatus{ReadyReplicas: 1},
},
newObj: &workloadv1alpha1.Workload{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "123498",
ManagedFields: []metav1.ManagedFieldsEntry{{}, {}},
},
Spec: workloadv1alpha1.WorkloadSpec{
Replicas: pointer.Int32(5),
Template: v1.PodTemplateSpec{},
Paused: false,
},
Status: workloadv1alpha1.WorkloadStatus{ReadyReplicas: 3},
},
wantChange: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
unstructuredOldObj, err := helper.ToUnstructured(tt.oldObj)
if err != nil {
klog.Errorf("Failed to transform oldObj, error: %v", err)
return
}
unstructuredNewObj, err := helper.ToUnstructured(tt.newObj)
if err != nil {
klog.Errorf("Failed to transform newObj, error: %v", err)
return
}
got := SpecificationChanged(unstructuredOldObj, unstructuredNewObj)
if tt.wantChange != got {
t.Fatalf("SpecificationChanged() got %v, want %v", got, tt.wantChange)
}
})
}
}