From 3ee9cee4bc195183cad1c3aa7a6c0a210c4624ca Mon Sep 17 00:00:00 2001 From: Alon Braymok <138359965+alonkeyval@users.noreply.github.com> Date: Wed, 4 Dec 2024 15:21:17 +0200 Subject: [PATCH] [GEN-1738] Add service name to instrumentation config (#1830) This pull request introduces changes to support the identification and management of service names in telemetry data. The most important changes include the addition of the `serviceName` attribute in various configurations and the implementation of logic to resolve and update service names based on workload annotations. ### Enhancements to Telemetry Data Management: * [`api/config/crd/bases/odigos.io_instrumentationconfigs.yaml`](diffhunk://#diff-5e561964988ce42093c5573e588c1d97439747cd6de9c31dca12336e37a90041R419-R422): Added `serviceName` attribute to identify the name of the service generating telemetry data. * [`api/odigos/v1alpha1/instrumentationconfig_types.go`](diffhunk://#diff-3586098ad3491bdc8fd28b720aab840e6e2200cf127b2d14770cd824c7a2f0e6R37-R39): Introduced `ServiceName` field in `InstrumentationConfigSpec` to store the service name. ### Updates to Controllers: * [`instrumentor/controllers/instrumentationconfig/common.go`](diffhunk://#diff-faaca3ff5749f73800bbad6c7ce74da05a0cc11d700170ce9d80cc2fecfd3339R4-R15): Modified `updateInstrumentationConfigForWorkload` to accept `serviceName` and added `resolveServiceName` function to determine the service name based on workload kind. [[1]](diffhunk://#diff-faaca3ff5749f73800bbad6c7ce74da05a0cc11d700170ce9d80cc2fecfd3339R4-R15) [[2]](diffhunk://#diff-faaca3ff5749f73800bbad6c7ce74da05a0cc11d700170ce9d80cc2fecfd3339R27-R28) [[3]](diffhunk://#diff-faaca3ff5749f73800bbad6c7ce74da05a0cc11d700170ce9d80cc2fecfd3339R248-R259) * [`instrumentor/controllers/instrumentationconfig/instrumentationrule_controller.go`](diffhunk://#diff-6ba19e15618d5bb0f412e90cff57a99397c83cb25c0e9cacf480a5fcc77793d1R37-R46): Integrated service name resolution in the `Reconcile` method and updated calls to `updateInstrumentationConfigForWorkload`. [[1]](diffhunk://#diff-6ba19e15618d5bb0f412e90cff57a99397c83cb25c0e9cacf480a5fcc77793d1R37-R46) [[2]](diffhunk://#diff-6ba19e15618d5bb0f412e90cff57a99397c83cb25c0e9cacf480a5fcc77793d1L47-R58) * [`instrumentor/controllers/instrumentationconfig/instrumentedapplication_controller.go`](diffhunk://#diff-4b1adbc472dd1da77a6fa9c8f2c978e515f046c7ab08472195d97cca5254d52aL48-R73): Added logic to resolve and use the service name in the `Reconcile` method. ### Testing Updates: * [`instrumentor/controllers/instrumentationconfig/common_test.go`](diffhunk://#diff-72fffbcddbe0811604eb4e532f7e6d24d9a9acebaa7889658b0672620e945a18L35-R35): Updated test cases to pass `serviceName` to `updateInstrumentationConfigForWorkload`. [[1]](diffhunk://#diff-72fffbcddbe0811604eb4e532f7e6d24d9a9acebaa7889658b0672620e945a18L35-R35) [[2]](diffhunk://#diff-72fffbcddbe0811604eb4e532f7e6d24d9a9acebaa7889658b0672620e945a18L75-R75) [[3]](diffhunk://#diff-72fffbcddbe0811604eb4e532f7e6d24d9a9acebaa7889658b0672620e945a18L122-R122) [[4]](diffhunk://#diff-72fffbcddbe0811604eb4e532f7e6d24d9a9acebaa7889658b0672620e945a18L153-R153) [[5]](diffhunk://#diff-72fffbcddbe0811604eb4e532f7e6d24d9a9acebaa7889658b0672620e945a18L190-R190) [[6]](diffhunk://#diff-72fffbcddbe0811604eb4e532f7e6d24d9a9acebaa7889658b0672620e945a18L240-R240) [[7]](diffhunk://#diff-72fffbcddbe0811604eb4e532f7e6d24d9a9acebaa7889658b0672620e945a18L306-R306) [[8]](diffhunk://#diff-72fffbcddbe0811604eb4e532f7e6d24d9a9acebaa7889658b0672620e945a18L363-R363) [[9]](diffhunk://#diff-72fffbcddbe0811604eb4e532f7e6d24d9a9acebaa7889658b0672620e945a18L415-R415) [[10]](diffhunk://#diff-72fffbcddbe0811604eb4e532f7e6d24d9a9acebaa7889658b0672620e945a18L479-R479) [[11]](diffhunk://#diff-72fffbcddbe0811604eb4e532f7e6d24d9a9acebaa7889658b0672620e945a18L570-R570) [[12]](diffhunk://#diff-72fffbcddbe0811604eb4e532f7e6d24d9a9acebaa7889658b0672620e945a18L633-R633) ### New Workload Reconciler: * [`instrumentor/controllers/instrumentationconfig/manager.go`](diffhunk://#diff-11469a5daa6aeffa1d7ef9fa54f8242b0d860ac2ee1342a74812503693684c69R5-R47): Added predicates and watchers for deployment, statefulset, and daemonset changes to handle service name updates. [[1]](diffhunk://#diff-11469a5daa6aeffa1d7ef9fa54f8242b0d860ac2ee1342a74812503693684c69R5-R47) [[2]](diffhunk://#diff-11469a5daa6aeffa1d7ef9fa54f8242b0d860ac2ee1342a74812503693684c69R60) [[3]](diffhunk://#diff-11469a5daa6aeffa1d7ef9fa54f8242b0d860ac2ee1342a74812503693684c69R73-R114) * [`instrumentor/controllers/instrumentationconfig/workloads_reconciler.go`](diffhunk://#diff-ba0fc2c892d12018b4a77def942688b6f4925c0f2775a8b7e1952476cd4e50caR1-R78): Implemented `WorkloadsReconciler` to manage `InstrumentationConfig` updates based on workload annotations. --------- Co-authored-by: alonkeyval --- .../odigos.io_instrumentationconfigs.yaml | 4 + .../v1alpha1/instrumentationconfigspec.go | 9 +++ .../v1alpha1/instrumentationconfig_types.go | 4 + .../instrumentationconfig/common.go | 18 ++++- .../instrumentationconfig/common_test.go | 24 +++--- .../instrumentationrule_controller.go | 13 +++- .../instrumentedapplication_controller.go | 14 +++- .../instrumentationconfig/manager.go | 42 +++++++++++ .../workload_predicate.go | 42 +++++++++++ .../workloads_controllers.go | 75 +++++++++++++++++++ k8sutils/pkg/workload/workload.go | 50 ++++++++++++- 11 files changed, 276 insertions(+), 19 deletions(-) create mode 100644 instrumentor/controllers/instrumentationconfig/workload_predicate.go create mode 100644 instrumentor/controllers/instrumentationconfig/workloads_controllers.go diff --git a/api/config/crd/bases/odigos.io_instrumentationconfigs.yaml b/api/config/crd/bases/odigos.io_instrumentationconfigs.yaml index b2f338f1d..fdfd06cea 100644 --- a/api/config/crd/bases/odigos.io_instrumentationconfigs.yaml +++ b/api/config/crd/bases/odigos.io_instrumentationconfigs.yaml @@ -416,6 +416,10 @@ spec: - payloadCollection type: object type: array + serviceName: + description: the service.name property is used to populate the `service.name` + resource attribute in the telemetry generated by this workload + type: string type: object status: properties: diff --git a/api/generated/odigos/applyconfiguration/odigos/v1alpha1/instrumentationconfigspec.go b/api/generated/odigos/applyconfiguration/odigos/v1alpha1/instrumentationconfigspec.go index 0a91a1d61..4c6b6fb4c 100644 --- a/api/generated/odigos/applyconfiguration/odigos/v1alpha1/instrumentationconfigspec.go +++ b/api/generated/odigos/applyconfiguration/odigos/v1alpha1/instrumentationconfigspec.go @@ -20,6 +20,7 @@ package v1alpha1 // InstrumentationConfigSpecApplyConfiguration represents a declarative configuration of the InstrumentationConfigSpec type for use // with apply. type InstrumentationConfigSpecApplyConfiguration struct { + ServiceName *string `json:"serviceName,omitempty"` RuntimeDetailsInvalidated *bool `json:"runtimeDetailsInvalidated,omitempty"` Config []WorkloadInstrumentationConfigApplyConfiguration `json:"config,omitempty"` SdkConfigs []SdkConfigApplyConfiguration `json:"sdkConfigs,omitempty"` @@ -31,6 +32,14 @@ func InstrumentationConfigSpec() *InstrumentationConfigSpecApplyConfiguration { return &InstrumentationConfigSpecApplyConfiguration{} } +// WithServiceName sets the ServiceName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ServiceName field is set to the value of the last call. +func (b *InstrumentationConfigSpecApplyConfiguration) WithServiceName(value string) *InstrumentationConfigSpecApplyConfiguration { + b.ServiceName = &value + return b +} + // WithRuntimeDetailsInvalidated sets the RuntimeDetailsInvalidated field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the RuntimeDetailsInvalidated field is set to the value of the last call. diff --git a/api/odigos/v1alpha1/instrumentationconfig_types.go b/api/odigos/v1alpha1/instrumentationconfig_types.go index 3b3b35ffd..273ca211c 100644 --- a/api/odigos/v1alpha1/instrumentationconfig_types.go +++ b/api/odigos/v1alpha1/instrumentationconfig_types.go @@ -34,6 +34,10 @@ type InstrumentationConfigStatus struct { // Config for the OpenTelemeetry SDKs that should be applied to a workload. // The workload is identified by the owner reference type InstrumentationConfigSpec struct { + + // the service.name property is used to populate the `service.name` resource attribute in the telemetry generated by this workload + ServiceName string `json:"serviceName,omitempty"` + // true when the runtime details are invalidated and should be recalculated RuntimeDetailsInvalidated bool `json:"runtimeDetailsInvalidated,omitempty"` diff --git a/instrumentor/controllers/instrumentationconfig/common.go b/instrumentor/controllers/instrumentationconfig/common.go index 335291516..22bed59d6 100644 --- a/instrumentor/controllers/instrumentationconfig/common.go +++ b/instrumentor/controllers/instrumentationconfig/common.go @@ -1,14 +1,18 @@ package instrumentationconfig import ( + "context" + "fmt" + odigosv1alpha1 "github.com/odigos-io/odigos/api/odigos/v1alpha1" "github.com/odigos-io/odigos/api/odigos/v1alpha1/instrumentationrules" "github.com/odigos-io/odigos/common" "github.com/odigos-io/odigos/instrumentor/controllers/utils" "github.com/odigos-io/odigos/k8sutils/pkg/workload" + "sigs.k8s.io/controller-runtime/pkg/client" ) -func updateInstrumentationConfigForWorkload(ic *odigosv1alpha1.InstrumentationConfig, ia *odigosv1alpha1.InstrumentedApplication, rules *odigosv1alpha1.InstrumentationRuleList) error { +func updateInstrumentationConfigForWorkload(ic *odigosv1alpha1.InstrumentationConfig, ia *odigosv1alpha1.InstrumentedApplication, rules *odigosv1alpha1.InstrumentationRuleList, serviceName string) error { workloadName, workloadKind, err := workload.ExtractWorkloadInfoFromRuntimeObjectName(ia.Name) if err != nil { @@ -20,6 +24,8 @@ func updateInstrumentationConfigForWorkload(ic *odigosv1alpha1.InstrumentationCo Kind: workloadKind, } + ic.Spec.ServiceName = serviceName + sdkConfigs := make([]odigosv1alpha1.SdkConfig, 0, len(ia.Spec.RuntimeDetails)) // create an empty sdk config for each detected programming language @@ -239,3 +245,13 @@ func mergeMessagingPayloadCollectionRules(rule1 *instrumentationrules.MessagingP func boolPtr(b bool) *bool { return &b } + +func resolveServiceName(ctx context.Context, k8sClient client.Client, workloadName string, namespace string, kind workload.WorkloadKind) (string, error) { + objectKey := client.ObjectKey{Name: workloadName, Namespace: namespace} + obj, err := workload.GetWorkloadObject(ctx, objectKey, kind, k8sClient) + + if err != nil { + return "", fmt.Errorf("failed to get workload object to resolve reported service name annotation. will use fallback service name: %w", err) + } + return workload.ExtractServiceNameFromAnnotations(obj.GetAnnotations(), workloadName), nil +} diff --git a/instrumentor/controllers/instrumentationconfig/common_test.go b/instrumentor/controllers/instrumentationconfig/common_test.go index c924168b6..c5bede439 100644 --- a/instrumentor/controllers/instrumentationconfig/common_test.go +++ b/instrumentor/controllers/instrumentationconfig/common_test.go @@ -32,7 +32,7 @@ func TestUpdateInstrumentationConfigForWorkload_SingleLanguage(t *testing.T) { }, } rules := &odigosv1.InstrumentationRuleList{} - err := updateInstrumentationConfigForWorkload(&ic, &ia, rules) + err := updateInstrumentationConfigForWorkload(&ic, &ia, rules, "service-name") if err != nil { t.Errorf("Expected nil error, got %v", err) } @@ -72,7 +72,7 @@ func TestUpdateInstrumentationConfigForWorkload_MultipleLanguages(t *testing.T) }, } rules := &odigosv1.InstrumentationRuleList{} - err := updateInstrumentationConfigForWorkload(&ic, &ia, rules) + err := updateInstrumentationConfigForWorkload(&ic, &ia, rules, "service-name") if err != nil { t.Errorf("Expected nil error, got %v", err) } @@ -119,7 +119,7 @@ func TestUpdateInstrumentationConfigForWorkload_IgnoreUnknownLanguage(t *testing }, } rules := &odigosv1.InstrumentationRuleList{} - err := updateInstrumentationConfigForWorkload(&ic, &ia, rules) + err := updateInstrumentationConfigForWorkload(&ic, &ia, rules, "service-name") if err != nil { t.Errorf("Expected nil error, got %v", err) } @@ -150,7 +150,7 @@ func TestUpdateInstrumentationConfigForWorkload_NoLanguages(t *testing.T) { }, } rules := &odigosv1.InstrumentationRuleList{} - err := updateInstrumentationConfigForWorkload(&ic, &ia, rules) + err := updateInstrumentationConfigForWorkload(&ic, &ia, rules, "service-name") if err != nil { t.Errorf("Expected nil error, got %v", err) } @@ -187,7 +187,7 @@ func TestUpdateInstrumentationConfigForWorkload_SameLanguageMultipleContainers(t }, } rules := &odigosv1.InstrumentationRuleList{} - err := updateInstrumentationConfigForWorkload(&ic, &ia, rules) + err := updateInstrumentationConfigForWorkload(&ic, &ia, rules, "service-name") if err != nil { t.Errorf("Expected nil error, got %v", err) } @@ -237,7 +237,7 @@ func TestUpdateInstrumentationConfigForWorkload_SingleMatchingRule(t *testing.T) }, }, } - err := updateInstrumentationConfigForWorkload(&ic, &ia, rules) + err := updateInstrumentationConfigForWorkload(&ic, &ia, rules, "service-name") if err != nil { t.Errorf("Expected nil error, got %v", err) } @@ -303,7 +303,7 @@ func TestUpdateInstrumentationConfigForWorkload_InWorkloadList(t *testing.T) { }, } - err := updateInstrumentationConfigForWorkload(&ic, &ia, rules) + err := updateInstrumentationConfigForWorkload(&ic, &ia, rules, "service-name") if err != nil { t.Errorf("Expected nil error, got %v", err) } @@ -360,7 +360,7 @@ func TestUpdateInstrumentationConfigForWorkload_NotInWorkloadList(t *testing.T) }, } - err := updateInstrumentationConfigForWorkload(&ic, &ia, rules) + err := updateInstrumentationConfigForWorkload(&ic, &ia, rules, "service-name") if err != nil { t.Errorf("Expected nil error, got %v", err) } @@ -412,7 +412,7 @@ func TestUpdateInstrumentationConfigForWorkload_DisabledRule(t *testing.T) { }, } - err := updateInstrumentationConfigForWorkload(&ic, &ia, rules) + err := updateInstrumentationConfigForWorkload(&ic, &ia, rules, "service-name") if err != nil { t.Errorf("Expected nil error, got %v", err) } @@ -476,7 +476,7 @@ func TestUpdateInstrumentationConfigForWorkload_MultipleDefaultRules(t *testing. }, } - err := updateInstrumentationConfigForWorkload(&ic, &ia, rules) + err := updateInstrumentationConfigForWorkload(&ic, &ia, rules, "service-name") if err != nil { t.Errorf("Expected nil error, got %v", err) } @@ -567,7 +567,7 @@ func TestUpdateInstrumentationConfigForWorkload_RuleForLibrary(t *testing.T) { }, } - err := updateInstrumentationConfigForWorkload(&ic, &ia, rules) + err := updateInstrumentationConfigForWorkload(&ic, &ia, rules, "service-name") if err != nil { t.Errorf("Expected nil error, got %v", err) } @@ -630,7 +630,7 @@ func TestUpdateInstrumentationConfigForWorkload_LibraryRuleOtherLanguage(t *test }, } - err := updateInstrumentationConfigForWorkload(&ic, &ia, rules) + err := updateInstrumentationConfigForWorkload(&ic, &ia, rules, "service-name") if err != nil { t.Errorf("Expected nil error, got %v", err) } diff --git a/instrumentor/controllers/instrumentationconfig/instrumentationrule_controller.go b/instrumentor/controllers/instrumentationconfig/instrumentationrule_controller.go index a5971964d..c2b0a7a47 100644 --- a/instrumentor/controllers/instrumentationconfig/instrumentationrule_controller.go +++ b/instrumentor/controllers/instrumentationconfig/instrumentationrule_controller.go @@ -4,6 +4,7 @@ import ( "context" odigosv1alpha1 "github.com/odigos-io/odigos/api/odigos/v1alpha1" + "github.com/odigos-io/odigos/k8sutils/pkg/workload" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" @@ -33,6 +34,16 @@ func (r *InstrumentationRuleReconciler) Reconcile(ctx context.Context, req ctrl. } for _, ia := range instrumentedApplications.Items { + workloadName, workloadKind, err := workload.ExtractWorkloadInfoFromRuntimeObjectName(ia.Name) + if err != nil { + return ctrl.Result{}, err + } + + serviceName, err := resolveServiceName(ctx, r.Client, workloadName, ia.Namespace, workloadKind) + if err != nil { + logger.Error(err, "error resolving service name", "workload", ia.Name) + continue + } ic := &odigosv1alpha1.InstrumentationConfig{} err = r.Client.Get(ctx, client.ObjectKey{Name: ia.Name, Namespace: ia.Namespace}, ic) if err != nil { @@ -44,7 +55,7 @@ func (r *InstrumentationRuleReconciler) Reconcile(ctx context.Context, req ctrl. } } - err := updateInstrumentationConfigForWorkload(ic, &ia, instrumentationRules) + err = updateInstrumentationConfigForWorkload(ic, &ia, instrumentationRules, serviceName) if err != nil { logger.Error(err, "error updating instrumentation config", "workload", ia.Name) continue diff --git a/instrumentor/controllers/instrumentationconfig/instrumentedapplication_controller.go b/instrumentor/controllers/instrumentationconfig/instrumentedapplication_controller.go index 9718e5b5a..009007705 100644 --- a/instrumentor/controllers/instrumentationconfig/instrumentedapplication_controller.go +++ b/instrumentor/controllers/instrumentationconfig/instrumentedapplication_controller.go @@ -21,6 +21,7 @@ import ( odigosv1 "github.com/odigos-io/odigos/api/odigos/v1alpha1" "github.com/odigos-io/odigos/k8sutils/pkg/utils" + "github.com/odigos-io/odigos/k8sutils/pkg/workload" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" @@ -54,13 +55,24 @@ func (r *InstrumentedApplicationReconciler) Reconcile(ctx context.Context, req c return ctrl.Result{}, err } + workloadName, workloadKind, err := workload.ExtractWorkloadInfoFromRuntimeObjectName(ia.Name) + if err != nil { + return ctrl.Result{}, err + } + + serviceName, err := resolveServiceName(ctx, r.Client, workloadName, ia.Namespace, workloadKind) + if err != nil { + logger.Error(err, "Failed to resolve service name", "workload", workloadName, "kind", workloadKind) + return ctrl.Result{}, err + } + instrumentationRules := &odigosv1.InstrumentationRuleList{} err = r.Client.List(ctx, instrumentationRules) if client.IgnoreNotFound(err) != nil { return ctrl.Result{}, err } - err = updateInstrumentationConfigForWorkload(&ic, &ia, instrumentationRules) + err = updateInstrumentationConfigForWorkload(&ic, &ia, instrumentationRules, serviceName) if err != nil { return ctrl.Result{}, err } diff --git a/instrumentor/controllers/instrumentationconfig/manager.go b/instrumentor/controllers/instrumentationconfig/manager.go index 17b4ba3ec..1a20b5718 100644 --- a/instrumentor/controllers/instrumentationconfig/manager.go +++ b/instrumentor/controllers/instrumentationconfig/manager.go @@ -2,11 +2,13 @@ package instrumentationconfig import ( odigosv1alpha1 "github.com/odigos-io/odigos/api/odigos/v1alpha1" + appsv1 "k8s.io/api/apps/v1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" ) func SetupWithManager(mgr ctrl.Manager) error { + // Watch InstrumentationRule err := builder. ControllerManagedBy(mgr). Named("instrumentor-instrumentationconfig-instrumentationrule"). @@ -19,6 +21,7 @@ func SetupWithManager(mgr ctrl.Manager) error { return err } + // Watch InstrumentedApplication err = builder. ControllerManagedBy(mgr). Named("instrumentor-instrumentationconfig-instrumentedapplication"). @@ -31,5 +34,44 @@ func SetupWithManager(mgr ctrl.Manager) error { return err } + // Watch for Deployment changes using DeploymentReconciler + if err := builder. + ControllerManagedBy(mgr). + Named("instrumentor-instrumentationconfig-deployment"). + For(&appsv1.Deployment{}). + WithEventFilter(workloadReportedNameAnnotationChanged{}). + Complete(&DeploymentReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }); err != nil { + return err + } + + // Watch for StatefulSet changes using StatefulSetReconciler + if err := builder. + ControllerManagedBy(mgr). + Named("instrumentor-instrumentationconfig-statefulset"). + For(&appsv1.StatefulSet{}). + WithEventFilter(workloadReportedNameAnnotationChanged{}). + Complete(&StatefulSetReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }); err != nil { + return err + } + + // Watch for DaemonSet changes using DaemonSetReconciler + if err := builder. + ControllerManagedBy(mgr). + Named("instrumentor-instrumentationconfig-daemonset"). + For(&appsv1.DaemonSet{}). + WithEventFilter(workloadReportedNameAnnotationChanged{}). + Complete(&DaemonSetReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }); err != nil { + return err + } + return nil } diff --git a/instrumentor/controllers/instrumentationconfig/workload_predicate.go b/instrumentor/controllers/instrumentationconfig/workload_predicate.go new file mode 100644 index 000000000..369141f27 --- /dev/null +++ b/instrumentor/controllers/instrumentationconfig/workload_predicate.go @@ -0,0 +1,42 @@ +package instrumentationconfig + +import ( + "github.com/odigos-io/odigos/common/consts" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +// workloadReportedNameAnnotationChanged is a custom predicate that detects changes +// to the `odigos.io/reported-name` annotation on workload resources such as +// Deployment, StatefulSet, and DaemonSet. This ensures that the controller +// reacts only when the specific annotation is updated. +type workloadReportedNameAnnotationChanged struct { + predicate.Funcs +} + +// the instrumentation config is create by the instrumented application controller +func (w workloadReportedNameAnnotationChanged) Create(e event.CreateEvent) bool { + return false +} + +func (w workloadReportedNameAnnotationChanged) Update(e event.UpdateEvent) bool { + if e.ObjectOld == nil || e.ObjectNew == nil { + return false + } + + oldAnnotations := e.ObjectOld.GetAnnotations() + newAnnotations := e.ObjectNew.GetAnnotations() + + oldName := oldAnnotations[consts.OdigosReportedNameAnnotation] + newName := newAnnotations[consts.OdigosReportedNameAnnotation] + + return oldName != newName +} + +func (w workloadReportedNameAnnotationChanged) Delete(e event.DeleteEvent) bool { + return false +} + +func (w workloadReportedNameAnnotationChanged) Generic(e event.GenericEvent) bool { + return false +} diff --git a/instrumentor/controllers/instrumentationconfig/workloads_controllers.go b/instrumentor/controllers/instrumentationconfig/workloads_controllers.go new file mode 100644 index 000000000..45b80e17d --- /dev/null +++ b/instrumentor/controllers/instrumentationconfig/workloads_controllers.go @@ -0,0 +1,75 @@ +package instrumentationconfig + +import ( + "context" + + odigosv1alpha1 "github.com/odigos-io/odigos/api/odigos/v1alpha1" + "github.com/odigos-io/odigos/k8sutils/pkg/utils" + "github.com/odigos-io/odigos/k8sutils/pkg/workload" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +// These controllers handle update of the InstrumentationConfig's ServiceName +// whenever there are changes in the associated workloads (Deployments, DaemonSets, StatefulSets). + +type DeploymentReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +func (r *DeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + return reconcileWorkload(ctx, r.Client, workload.WorkloadKindDeployment, req) +} + +type DaemonSetReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +func (r *DaemonSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + return reconcileWorkload(ctx, r.Client, workload.WorkloadKindDaemonSet, req) +} + +type StatefulSetReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +func (r *StatefulSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + return reconcileWorkload(ctx, r.Client, workload.WorkloadKindStatefulSet, req) +} + +func reconcileWorkload(ctx context.Context, k8sClient client.Client, objKind workload.WorkloadKind, req ctrl.Request) (ctrl.Result, error) { + serviceName, err := resolveServiceName(ctx, k8sClient, req.Name, req.Namespace, objKind) + if err != nil { + return ctrl.Result{}, err + } + instConfigName := workload.CalculateWorkloadRuntimeObjectName(req.Name, objKind) + return updateInstrumentationConfigServiceName(ctx, k8sClient, instConfigName, req.Namespace, serviceName) + +} + +func updateInstrumentationConfigServiceName(ctx context.Context, k8sClient client.Client, instConfigName, namespace string, serviceName string) (reconcile.Result, error) { + logger := log.FromContext(ctx) + + instConfig := &odigosv1alpha1.InstrumentationConfig{} + err := k8sClient.Get(ctx, types.NamespacedName{Name: instConfigName, Namespace: namespace}, instConfig) + if err != nil { + return reconcile.Result{}, client.IgnoreNotFound(err) + } + + if instConfig.Spec.ServiceName != serviceName { + instConfig.Spec.ServiceName = serviceName + + logger.Info("Updating InstrumentationConfig", "name", instConfigName, "namespace", namespace) + err = k8sClient.Update(ctx, instConfig) + return utils.K8SUpdateErrorHandler(err) + } + + return reconcile.Result{}, nil +} diff --git a/k8sutils/pkg/workload/workload.go b/k8sutils/pkg/workload/workload.go index fa860a50d..d8b6369a8 100644 --- a/k8sutils/pkg/workload/workload.go +++ b/k8sutils/pkg/workload/workload.go @@ -4,13 +4,14 @@ import ( "context" "errors" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "sigs.k8s.io/controller-runtime/pkg/log" - "github.com/odigos-io/odigos/common/consts" + v1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" ) type Workload interface { @@ -157,3 +158,44 @@ func GetInstrumentationLabelTexts(workloadLabels map[string]string, workloadKind return } + +func GetWorkloadObject(ctx context.Context, objectKey client.ObjectKey, kind WorkloadKind, kubeClient client.Client) (metav1.Object, error) { + switch kind { + case WorkloadKindDeployment: + var deployment v1.Deployment + err := kubeClient.Get(ctx, objectKey, &deployment) + if err != nil { + return nil, err + } + return &deployment, nil + + case WorkloadKindStatefulSet: + var statefulSet v1.StatefulSet + err := kubeClient.Get(ctx, objectKey, &statefulSet) + if err != nil { + return nil, err + } + return &statefulSet, nil + + case WorkloadKindDaemonSet: + var daemonSet v1.DaemonSet + err := kubeClient.Get(ctx, objectKey, &daemonSet) + if err != nil { + return nil, err + } + return &daemonSet, nil + + default: + return nil, errors.New("failed to get workload object for kind: " + string(kind)) + } +} + +func ExtractServiceNameFromAnnotations(annotations map[string]string, defaultName string) string { + if annotations == nil { + return defaultName + } + if reportedName, exists := annotations[consts.OdigosReportedNameAnnotation]; exists && reportedName != "" { + return reportedName + } + return defaultName +}