Skip to content

Commit

Permalink
feat: add 'runtime detection' condition to ic status (#2420)
Browse files Browse the repository at this point in the history
Show status about runtime detection in instrumentation config.

<img width="670" alt="image"
src="https://github.com/user-attachments/assets/d7f7e2a7-736e-46b9-89f5-e40b8ff2bf49"
/>

<img width="670" alt="image"
src="https://github.com/user-attachments/assets/fbd6c7ed-c5ee-43c3-8139-a7a8e501cf31"
/>
  • Loading branch information
blumamir authored Feb 10, 2025
1 parent ed41a70 commit 62ef9ab
Show file tree
Hide file tree
Showing 13 changed files with 211 additions and 47 deletions.
19 changes: 17 additions & 2 deletions api/odigos/v1alpha1/instrumentationconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const (
// Define a status condition type that describes why the workload is marked for instrumentation or not.
MarkedForInstrumentationStatusConditionType = "MarkedForInstrumentation"
// Describe the runtime detection status of this workload.
RuntimeDetectionStatusConditionType = "RuntimeDetection" // TODO: placeholder, not yet implemented
RuntimeDetectionStatusConditionType = "RuntimeDetection"
// this const is the Type field in the conditions of the InstrumentationConfigStatus.
AgentEnabledStatusConditionType = "AgentEnabled"
// reports whether the workload associated with the InstrumentationConfig has been rolled out.
Expand All @@ -38,7 +38,6 @@ func StatusConditionTypeLogicalOrder(condType string) int {
case MarkedForInstrumentationStatusConditionType:
return 1
case RuntimeDetectionStatusConditionType:
// TODO: placeholder, not yet implemented
return 2
case AgentEnabledStatusConditionType:
return 3
Expand Down Expand Up @@ -71,6 +70,22 @@ const (
MarkedForInstrumentationReasonError MarkedForInstrumentationReason = "RetirableError"
)

// +kubebuilder:validation:Enum=DetectedSuccessfully;WaitingForDetection;NoRunningPods;Error
type RuntimeDetectionReason string

const (
// when the runtime detection process is successful and runtime details are available for instrumentation.
RuntimeDetectionReasonDetectedSuccessfully RuntimeDetectionReason = "DetectedSuccessfully"
// when the runtime detection process is still ongoing and the runtime details are not yet available.
// this status should be visible only for a short period of time until the detection process is completed by one odiglet.
RuntimeDetectionReasonWaitingForDetection RuntimeDetectionReason = "WaitingForDetection"
// when the runtime detection process is not yet started because there are no running pods for this workload.
// runtime detection requires at least one running pod to inspect the runtime details from.
RuntimeDetectionReasonNoRunningPods RuntimeDetectionReason = "NoRunningPods"
// error occurred during the runtime detection process.
RuntimeDetectionReasonError RuntimeDetectionReason = "Error"
)

// +kubebuilder:validation:Enum=EnabledSuccessfully;WaitingForRuntimeInspection;WaitingForNodeCollector;UnsupportedProgrammingLanguage;IgnoredContainer;NoAvailableAgent;UnsupportedRuntimeVersion;MissingDistroParameter;OtherAgentDetected
type AgentEnabledReason string

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
func reconcileWorkloadObject(ctx context.Context, kubeClient client.Client, workloadObject client.Object) error {
logger := log.FromContext(ctx)

instrumented, _, _, err := sourceutils.IsObjectInstrumentedBySource(ctx, kubeClient, workloadObject)
instrumented, _, err := sourceutils.IsObjectInstrumentedBySource(ctx, kubeClient, workloadObject)
if err != nil {
return err
}
Expand Down Expand Up @@ -148,7 +148,7 @@ func syncGenericWorkloadListToNs(ctx context.Context, c client.Client, kind k8sc
}
}

instrumented, _, _, err := sourceutils.IsObjectInstrumentedBySource(ctx, c, freshWorkloadCopy)
instrumented, _, err := sourceutils.IsObjectInstrumentedBySource(ctx, c, freshWorkloadCopy)
if err != nil {
return err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func (r *InstrumentationConfigReconciler) Reconcile(ctx context.Context, req ctr
return ctrl.Result{}, err
}

enabled, _, _, err := sourceutils.IsObjectInstrumentedBySource(ctx, r.Client, workloadObject)
enabled, _, err := sourceutils.IsObjectInstrumentedBySource(ctx, r.Client, workloadObject)
if err != nil {
return ctrl.Result{}, err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func (r *NamespaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
return ctrl.Result{}, err
}

enabled, _, _, err := sourceutils.IsObjectInstrumentedBySource(ctx, r.Client, &ns)
enabled, _, err := sourceutils.IsObjectInstrumentedBySource(ctx, r.Client, &ns)
if err != nil {
return ctrl.Result{}, err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func (r *SourceReconciler) syncWorkload(ctx context.Context, source *v1alpha1.So
return err
}

instrumented, _, _, err := sourceutils.IsObjectInstrumentedBySource(ctx, r.Client, obj)
instrumented, _, err := sourceutils.IsObjectInstrumentedBySource(ctx, r.Client, obj)
if err != nil {
return err
}
Expand Down
46 changes: 46 additions & 0 deletions instrumentor/controllers/startlangdetection/conditionutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package startlangdetection

import (
"slices"
"time"

odigosv1 "github.com/odigos-io/odigos/api/odigos/v1alpha1"
"github.com/odigos-io/odigos/k8sutils/pkg/workload"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

Expand All @@ -28,3 +31,46 @@ func sortIcConditionsByLogicalOrder(conditions []metav1.Condition) []metav1.Cond
})
return conditions
}

func initiateRuntimeDetailsConditionIfMissing(ic *odigosv1.InstrumentationConfig, workloadObj workload.Workload) bool {

if meta.FindStatusCondition(ic.Status.Conditions, odigosv1.RuntimeDetectionStatusConditionType) != nil {
// avoid adding the condition if it already exists
return false
}

// migration code, add this condition to previous instrumentation configs
// which were created before this condition was introduced
if len(ic.Status.RuntimeDetailsByContainer) > 0 {
ic.Status.Conditions = append(ic.Status.Conditions, metav1.Condition{
Type: odigosv1.RuntimeDetectionStatusConditionType,
Status: metav1.ConditionTrue,
Reason: string(odigosv1.RuntimeDetectionReasonWaitingForDetection),
Message: "runtime detection completed successfully",
LastTransitionTime: metav1.NewTime(time.Now()),
})
return true
}

// if the workload has no available replicas, we can't detect the runtime
if workloadObj.AvailableReplicas() == 0 {
ic.Status.Conditions = append(ic.Status.Conditions, metav1.Condition{
Type: odigosv1.RuntimeDetectionStatusConditionType,
Status: metav1.ConditionFalse,
Reason: string(odigosv1.RuntimeDetectionReasonNoRunningPods),
Message: "No running pods available to detect source runtime",
LastTransitionTime: metav1.NewTime(time.Now()),
})
return true
}

ic.Status.Conditions = append(ic.Status.Conditions, metav1.Condition{
Type: odigosv1.RuntimeDetectionStatusConditionType,
Status: metav1.ConditionUnknown,
Reason: string(odigosv1.RuntimeDetectionReasonWaitingForDetection),
Message: "Waiting for odiglet to initiate runtime detection in a node with running pod",
LastTransitionTime: metav1.NewTime(time.Now()),
})

return true
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func (n *NamespacesReconciler) Reconcile(ctx context.Context, request ctrl.Reque
return ctrl.Result{}, client.IgnoreNotFound(err)
}

enabled, _, _, err := sourceutils.IsObjectInstrumentedBySource(ctx, n.Client, &ns)
enabled, _, err := sourceutils.IsObjectInstrumentedBySource(ctx, n.Client, &ns)
if err != nil {
return ctrl.Result{}, err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,12 @@ func reconcileWorkload(ctx context.Context, k8sClient client.Client, objKind k8s
return ctrl.Result{}, err
}

enabled, reason, message, err := sourceutils.IsObjectInstrumentedBySource(ctx, k8sClient, obj)
workloadObj, err := workload.ObjectToWorkload(obj)
if err != nil {
return ctrl.Result{}, err
}

enabled, markedForInstrumentationCondition, err := sourceutils.IsObjectInstrumentedBySource(ctx, k8sClient, obj)
if err != nil {
return ctrl.Result{}, err
}
Expand All @@ -78,14 +83,10 @@ func reconcileWorkload(ctx context.Context, k8sClient client.Client, objKind k8s
}
}

cond := metav1.Condition{
Type: odigosv1.MarkedForInstrumentationStatusConditionType,
Status: metav1.ConditionTrue, // if instrumentation config is created, it is always instrumented.
Reason: string(reason),
Message: message,
}
statuschanged := meta.SetStatusCondition(&ic.Status.Conditions, cond)
if statuschanged {
markedForInstChanged := meta.SetStatusCondition(&ic.Status.Conditions, markedForInstrumentationCondition)
runtimeDetailsChanged := initiateRuntimeDetailsConditionIfMissing(ic, workloadObj)

if markedForInstChanged || runtimeDetailsChanged {
logger.Info("Updating initial instrumentation status condition of InstrumentationConfig", "name", instConfigName, "namespace", req.Namespace)
if !areConditionsLogicallySorted(ic.Status.Conditions) {
// it is possible that by the time we are running this code, the status conditions are updated by another controller
Expand Down
49 changes: 38 additions & 11 deletions k8sutils/pkg/source/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"context"
"fmt"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

odigosv1 "github.com/odigos-io/odigos/api/odigos/v1alpha1"
k8sutils "github.com/odigos-io/odigos/k8sutils/pkg/utils"

Expand All @@ -17,39 +19,64 @@ import (
// 4) False
func IsObjectInstrumentedBySource(ctx context.Context,
k8sClient client.Client,
obj client.Object) (bool, odigosv1.MarkedForInstrumentationReason, string, error) {
obj client.Object) (bool, metav1.Condition, error) {
// Check if a Source object exists for this object
sources, err := odigosv1.GetSources(ctx, k8sClient, obj)
if err != nil {
reason := odigosv1.MarkedForInstrumentationReasonError
return false, reason, "cannot determine if workload is marked for instrumentation due to error", err
condition := metav1.Condition{
Type: odigosv1.MarkedForInstrumentationStatusConditionType,
Status: metav1.ConditionUnknown,
Reason: string(odigosv1.MarkedForInstrumentationReasonError),
Message: "cannot determine if workload is marked for instrumentation due to error: " + err.Error(),
}
return false, condition, err
}

if sources.Workload != nil {
if !odigosv1.IsDisabledSource(sources.Workload) && !k8sutils.IsTerminating(sources.Workload) {
reason := odigosv1.MarkedForInstrumentationReasonWorkloadSource
message := fmt.Sprintf("workload marked for automatic instrumentation by workload source CR '%s' in namespace '%s'",
sources.Workload.Name, sources.Workload.Namespace)
return true, reason, message, nil
condition := metav1.Condition{
Type: odigosv1.MarkedForInstrumentationStatusConditionType,
Status: metav1.ConditionTrue,
Reason: string(odigosv1.MarkedForInstrumentationReasonWorkloadSource),
Message: message,
}
return true, condition, nil
}
if odigosv1.IsDisabledSource(sources.Workload) && !k8sutils.IsTerminating(sources.Workload) {
reason := odigosv1.MarkedForInstrumentationReasonWorkloadSourceDisabled
message := fmt.Sprintf("workload marked to disable instrumentation by workload source CR '%s' in namespace '%s'",
sources.Workload.Name, sources.Workload.Namespace)
return false, reason, message, nil
condition := metav1.Condition{
Type: odigosv1.MarkedForInstrumentationStatusConditionType,
Status: metav1.ConditionFalse,
Reason: string(odigosv1.MarkedForInstrumentationReasonWorkloadSourceDisabled),
Message: message,
}
return false, condition, nil
}
}

if sources.Namespace != nil && !odigosv1.IsDisabledSource(sources.Namespace) && !k8sutils.IsTerminating(sources.Namespace) {
reason := odigosv1.MarkedForInstrumentationReasonNamespaceSource
message := fmt.Sprintf("workload marked for automatic instrumentation by namespace source CR '%s' in namespace '%s'",
sources.Namespace.Name, sources.Namespace.Namespace)
return true, reason, message, nil
condition := metav1.Condition{
Type: odigosv1.MarkedForInstrumentationStatusConditionType,
Status: metav1.ConditionTrue,
Reason: string(reason),
Message: message,
}
return true, condition, nil
}

reason := odigosv1.MarkedForInstrumentationReasonNoSource
message := "workload not marked for automatic instrumentation by any source CR"
return false, reason, message, nil
condition := metav1.Condition{
Type: odigosv1.MarkedForInstrumentationStatusConditionType,
Status: metav1.ConditionFalse,
Reason: string(odigosv1.MarkedForInstrumentationReasonNoSource),
Message: "workload not marked for automatic instrumentation by any source CR",
}
return false, condition, nil
}

// IsSourceRelevant returns true if a Source:
Expand Down
28 changes: 15 additions & 13 deletions odiglet/pkg/kube/runtime_details/inspection.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package runtime_details

import (
"context"
"encoding/json"
"fmt"
"strings"

Expand All @@ -22,7 +21,8 @@ import (
"github.com/odigos-io/odigos/odiglet/pkg/log"
"github.com/odigos-io/odigos/procdiscovery/pkg/inspectors"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)

Expand Down Expand Up @@ -223,8 +223,10 @@ func checkEnvVarsInContainerManifest(container corev1.Container, envVarNames []s
return false
}

func persistRuntimeDetailsToInstrumentationConfig(ctx context.Context, kubeclient client.Client, instrumentationConfig *odigosv1.InstrumentationConfig, newStatus odigosv1.InstrumentationConfigStatus) error {
// This come to make sure we're updating instrumentationConfig only once (at the first time)
func persistRuntimeDetailsToInstrumentationConfig(ctx context.Context, kubeclient client.Client, instrumentationConfig *odigosv1.InstrumentationConfig, newRuntimeDetials []odigosv1.RuntimeDetailsByContainer) error {

// fetch a fresh copy of instrumentation config.
// TODO: is this necessary? can we do it with the existing object?
currentConfig := &odigosv1.InstrumentationConfig{}
err := kubeclient.Get(ctx, client.ObjectKeyFromObject(instrumentationConfig), currentConfig)
if err != nil {
Expand All @@ -237,15 +239,15 @@ func persistRuntimeDetailsToInstrumentationConfig(ctx context.Context, kubeclien
return nil
}

// persist the runtime results into the status of the instrumentation config
patchStatus := odigosv1.InstrumentationConfig{
Status: newStatus,
}
patchData, err := json.Marshal(patchStatus)
if err != nil {
return err
}
err = kubeclient.Status().Patch(ctx, instrumentationConfig, client.RawPatch(types.MergePatchType, patchData), client.FieldOwner("odiglet-runtimedetails"))
currentConfig.Status.RuntimeDetailsByContainer = newRuntimeDetials
meta.SetStatusCondition(&currentConfig.Status.Conditions, metav1.Condition{
Type: odigosv1.RuntimeDetectionStatusConditionType,
Status: metav1.ConditionTrue,
Reason: string(odigosv1.RuntimeDetectionReasonDetectedSuccessfully),
Message: "runtime detection completed successfully",
})

err = kubeclient.Status().Update(ctx, currentConfig)
if err != nil {
return err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,17 @@ func (r *InstrumentationConfigReconciler) Reconcile(ctx context.Context, request
}
}

if len(selectedPods) == 0 {
// this node is not running any pods managed by the workload, so nothing to do
return reconcile.Result{}, nil
}

runtimeResults, err := runtimeInspection(ctx, selectedPods, r.CriClient)
if err != nil {
return reconcile.Result{}, err
}

err = persistRuntimeDetailsToInstrumentationConfig(ctx, r.Client, &instrumentationConfig, odigosv1.InstrumentationConfigStatus{
RuntimeDetailsByContainer: runtimeResults,
})
err = persistRuntimeDetailsToInstrumentationConfig(ctx, r.Client, &instrumentationConfig, runtimeResults)
if err != nil {
return reconcile.Result{}, err
}
Expand Down
4 changes: 1 addition & 3 deletions odiglet/pkg/kube/runtime_details/pods_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,7 @@ func (p *PodsReconciler) Reconcile(ctx context.Context, request reconcile.Reques
return reconcile.Result{}, err
}

err = persistRuntimeDetailsToInstrumentationConfig(ctx, p.Client, &instrumentationConfig, odigosv1.InstrumentationConfigStatus{
RuntimeDetailsByContainer: runtimeResults,
})
err = persistRuntimeDetailsToInstrumentationConfig(ctx, p.Client, &instrumentationConfig, runtimeResults)
if err != nil {
return reconcile.Result{}, err
}
Expand Down
Loading

0 comments on commit 62ef9ab

Please sign in to comment.