From f43f67367cbdda4b1ab58409ccd8b848824de2e8 Mon Sep 17 00:00:00 2001 From: hanweisen Date: Thu, 9 Jun 2022 22:25:34 +0800 Subject: [PATCH] add syncEvent from execution_controller to resource Signed-off-by: hanweisen --- pkg/apis/work/v1alpha2/well_known_labels.go | 10 ++ .../execution/execution_controller.go | 15 ++- pkg/util/helper/work.go | 37 ++++++++ pkg/util/helper/work_test.go | 93 +++++++++++++++++++ 4 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 pkg/util/helper/work_test.go diff --git a/pkg/apis/work/v1alpha2/well_known_labels.go b/pkg/apis/work/v1alpha2/well_known_labels.go index 5588d6dfc..44b1df42f 100644 --- a/pkg/apis/work/v1alpha2/well_known_labels.go +++ b/pkg/apis/work/v1alpha2/well_known_labels.go @@ -40,3 +40,13 @@ const ( // ResourceConflictResolutionOverwrite is the value of ResourceConflictResolutionAnnotation, indicating the overwrite strategy. ResourceConflictResolutionOverwrite = "overwrite" ) + +// Define annotations that are added to the resource template. +const ( + // ResourceTemplateUIDAnnotation is the annotation that is added to the manifest in the Work object. + // The annotation is used to identify the resource template which the manifest is derived from. + // The annotation can also be used to fire events when syncing Work to member clusters. + // For more details about UID, please refer to: + // https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + ResourceTemplateUIDAnnotation = "resourcetemplate.karmada.io/uid" +) diff --git a/pkg/controllers/execution/execution_controller.go b/pkg/controllers/execution/execution_controller.go index 839d169f2..b67a53d65 100644 --- a/pkg/controllers/execution/execution_controller.go +++ b/pkg/controllers/execution/execution_controller.go @@ -196,7 +196,7 @@ func (c *Controller) syncToClusters(clusterName string, work *workv1alpha1.Work) err = c.tryUpdateWorkload(clusterName, workload) if err != nil { klog.Errorf("Failed to update resource(%v/%v) in the given member cluster %s, err is %v", workload.GetNamespace(), workload.GetName(), clusterName, err) - c.EventRecorder.Eventf(workload, corev1.EventTypeWarning, workv1alpha1.EventReasonSyncWorkFailed, "Failed to update resource(%v/%v) in the given member cluster %s, err is %v", workload.GetNamespace(), workload.GetName(), clusterName, err) + c.eventf(workload, corev1.EventTypeWarning, workv1alpha1.EventReasonSyncWorkFailed, "Failed to update resource(%v/%v) in the given member cluster %s, err is %v", workload.GetNamespace(), workload.GetName(), clusterName, err) errs = append(errs, err) continue } @@ -204,12 +204,12 @@ func (c *Controller) syncToClusters(clusterName string, work *workv1alpha1.Work) err = c.tryCreateWorkload(clusterName, workload) if err != nil { klog.Errorf("Failed to create resource(%v/%v) in the given member cluster %s, err is %v", workload.GetNamespace(), workload.GetName(), clusterName, err) - c.EventRecorder.Eventf(workload, corev1.EventTypeWarning, workv1alpha1.EventReasonSyncWorkFailed, "Failed to create resource(%v/%v) in the given member cluster %s, err is %v", workload.GetNamespace(), workload.GetName(), clusterName, err) + c.eventf(workload, corev1.EventTypeWarning, workv1alpha1.EventReasonSyncWorkFailed, "Failed to create resource(%v/%v) in the given member cluster %s, err is %v", workload.GetNamespace(), workload.GetName(), clusterName, err) errs = append(errs, err) continue } } - c.EventRecorder.Eventf(workload, corev1.EventTypeNormal, workv1alpha1.EventReasonSyncWorkSucceed, "Successfully applied resource(%v/%v) to cluster %s", workload.GetNamespace(), workload.GetName(), clusterName) + c.eventf(workload, corev1.EventTypeNormal, workv1alpha1.EventReasonSyncWorkSucceed, "Successfully applied resource(%v/%v) to cluster %s", workload.GetNamespace(), workload.GetName(), clusterName) syncSucceedNum++ } @@ -292,3 +292,12 @@ func (c *Controller) updateAppliedCondition(work *workv1alpha1.Work, status meta return updateErr }) } + +func (c *Controller) eventf(object *unstructured.Unstructured, eventtype, reason, messageFmt string, args ...interface{}) { + ref, err := helper.GenEventRef(object) + if err != nil { + klog.Errorf("ignore event(%s) as failed to build event reference for: kind=%s, %s/%s due to %v", reason, object.GetKind(), object.GetNamespace(), object.GetName(), err) + return + } + c.EventRecorder.Eventf(ref, eventtype, reason, messageFmt, args...) +} diff --git a/pkg/util/helper/work.go b/pkg/util/helper/work.go index 0b50b4cca..c9a936180 100644 --- a/pkg/util/helper/work.go +++ b/pkg/util/helper/work.go @@ -2,22 +2,28 @@ package helper import ( "context" + "fmt" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/retry" "k8s.io/klog/v2" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" workv1alpha1 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha1" + workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2" + "github.com/karmada-io/karmada/pkg/util" ) // 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() + util.MergeAnnotation(workload, workv1alpha2.ResourceTemplateUIDAnnotation, string(workload.GetUID())) workloadJSON, err := workload.MarshalJSON() if err != nil { klog.Errorf("Failed to marshal workload(%s/%s), Error: %v", workload.GetNamespace(), workload.GetName(), err) @@ -76,3 +82,34 @@ func GetWorksByLabelsSet(c client.Client, ls labels.Set) (*workv1alpha1.WorkList return workList, c.List(context.TODO(), workList, listOpt) } + +// GenEventRef returns the event reference. sets the UID(.spec.uid) that might be missing for fire events. +// Do nothing if the UID already exist, otherwise set the UID from annotation. +func GenEventRef(resource *unstructured.Unstructured) (*corev1.ObjectReference, error) { + ref := &corev1.ObjectReference{ + Kind: resource.GetKind(), + Namespace: resource.GetNamespace(), + Name: resource.GetName(), + UID: resource.GetUID(), + APIVersion: resource.GetAPIVersion(), + } + + if len(resource.GetUID()) == 0 { + uid := util.GetAnnotationValue(resource.GetAnnotations(), workv1alpha2.ResourceTemplateUIDAnnotation) + ref.UID = types.UID(uid) + } + + if len(ref.UID) == 0 { + return nil, fmt.Errorf("missing mandatory uid") + } + + if len(ref.Name) == 0 { + return nil, fmt.Errorf("missing mandatory name") + } + + if len(ref.Kind) == 0 { + return nil, fmt.Errorf("missing mandatory kind") + } + + return ref, nil +} diff --git a/pkg/util/helper/work_test.go b/pkg/util/helper/work_test.go new file mode 100644 index 000000000..0c0f07af8 --- /dev/null +++ b/pkg/util/helper/work_test.go @@ -0,0 +1,93 @@ +package helper + +import ( + "reflect" + "testing" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func TestGenEventRef(t *testing.T) { + tests := []struct { + name string + obj *unstructured.Unstructured + want *corev1.ObjectReference + wantErr bool + }{ + { + name: "has metadata.uid", + obj: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "demo-deployment", + "uid": "9249d2e7-3169-4c5f-be82-163bd80aa3cf", + }, + "spec": map[string]interface{}{ + "replicas": 2, + }, + }, + }, + want: &corev1.ObjectReference{ + Kind: "Deployment", + APIVersion: "apps/v1", + Name: "demo-deployment", + UID: "9249d2e7-3169-4c5f-be82-163bd80aa3cf", + }, + wantErr: false, + }, + { + name: "missing metadata.uid but has resourcetemplate.karmada.io/uid annontation", + obj: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "demo-deployment", + "annotations": map[string]interface{}{"resourcetemplate.karmada.io/uid": "9249d2e7-3169-4c5f-be82-163bd80aa3cf"}, + }, + "spec": map[string]interface{}{ + "replicas": 2, + }, + }, + }, + want: &corev1.ObjectReference{ + Kind: "Deployment", + APIVersion: "apps/v1", + Name: "demo-deployment", + UID: "9249d2e7-3169-4c5f-be82-163bd80aa3cf", + }, + wantErr: false, + }, + { + name: "missing metadata.uid and metadata.annotations", + obj: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "demo-deployment", + }, + "spec": map[string]interface{}{ + "replicas": 2, + }, + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual, err := GenEventRef(tt.obj) + if (err != nil) != tt.wantErr { + t.Errorf("GenEventRef() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(actual, tt.want) { + t.Errorf("GenEventRef() = %v, want %v", actual, tt.want) + } + }) + } +}