Skip to content

Commit 062b810

Browse files
committed
feat(tracing): add operator.impvmsnapshot reconciler spans
Instruments ImpVMSnapshotReconciler with three OTel spans: operator.impvmsnapshot.reconcile (root), operator.impvmsnapshot.retention, and operator.impvmsnapshot.cron_trigger, all with snap.name/snap.namespace attributes and error recording via tracing.RecordError.
1 parent 5cf26b3 commit 062b810

1 file changed

Lines changed: 59 additions & 28 deletions

File tree

internal/controller/impvmsnapshot_controller.go

Lines changed: 59 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import (
66
"time"
77

88
"github.com/robfig/cron/v3"
9+
"go.opentelemetry.io/otel"
10+
"go.opentelemetry.io/otel/attribute"
11+
"go.opentelemetry.io/otel/trace"
912
corev1 "k8s.io/api/core/v1"
1013
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1114
"k8s.io/apimachinery/pkg/runtime"
@@ -14,6 +17,7 @@ import (
1417
logf "sigs.k8s.io/controller-runtime/pkg/log"
1518

1619
impv1alpha1 "github.com/syscode-labs/imp/api/v1alpha1"
20+
"github.com/syscode-labs/imp/internal/tracing"
1721
)
1822

1923
// ImpVMSnapshotReconciler reconciles ImpVMSnapshot objects.
@@ -28,14 +32,21 @@ type ImpVMSnapshotReconciler struct {
2832

2933
var cronParser = cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
3034

31-
func (r *ImpVMSnapshotReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
35+
func (r *ImpVMSnapshotReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) {
3236
log := logf.FromContext(ctx)
3337

3438
snap := &impv1alpha1.ImpVMSnapshot{}
3539
if err := r.Get(ctx, req.NamespacedName, snap); err != nil {
3640
return ctrl.Result{}, client.IgnoreNotFound(err)
3741
}
3842

43+
ctx, span := otel.Tracer("imp.operator").Start(ctx, "operator.impvmsnapshot.reconcile",
44+
trace.WithAttributes(
45+
attribute.String("snap.name", req.Name),
46+
attribute.String("snap.namespace", req.Namespace),
47+
))
48+
defer func() { tracing.RecordError(span, err); span.End() }()
49+
3950
// Design rule 1: only reconcile parent objects (no LabelSnapshotParent label).
4051
if _, isChild := snap.Labels[impv1alpha1.LabelSnapshotParent]; isChild {
4152
return ctrl.Result{}, nil
@@ -70,35 +81,47 @@ func (r *ImpVMSnapshotReconciler) Reconcile(ctx context.Context, req ctrl.Reques
7081
}
7182

7283
// Design rule 5: retention pruning — sort by creationTimestamp ascending, prune oldest beyond retention.
73-
retention := int(snap.Spec.Retention)
74-
if retention == 0 {
75-
retention = 3 // default
76-
}
77-
if len(children) > retention {
78-
sort.Slice(children, func(i, j int) bool {
79-
return children[i].CreationTimestamp.Before(&children[j].CreationTimestamp)
80-
})
81-
toDelete := children[:len(children)-retention]
82-
for i := range toDelete {
83-
// Design rule 5: never delete the baseSnapshot child.
84-
if snap.Spec.BaseSnapshot != "" && toDelete[i].Name == snap.Spec.BaseSnapshot {
85-
continue
84+
{
85+
retCtx, retSpan := otel.Tracer("imp.operator").Start(ctx, "operator.impvmsnapshot.retention",
86+
trace.WithAttributes(
87+
attribute.String("snap.name", req.Name),
88+
attribute.String("snap.namespace", req.Namespace),
89+
))
90+
retention := int(snap.Spec.Retention)
91+
if retention == 0 {
92+
retention = 3 // default
93+
}
94+
if len(children) > retention {
95+
sort.Slice(children, func(i, j int) bool {
96+
return children[i].CreationTimestamp.Before(&children[j].CreationTimestamp)
97+
})
98+
toDelete := children[:len(children)-retention]
99+
for i := range toDelete {
100+
// Design rule 5: never delete the baseSnapshot child.
101+
if snap.Spec.BaseSnapshot != "" && toDelete[i].Name == snap.Spec.BaseSnapshot {
102+
continue
103+
}
104+
if err := r.Delete(retCtx, &toDelete[i]); client.IgnoreNotFound(err) != nil {
105+
tracing.RecordError(retSpan, err)
106+
retSpan.End()
107+
return ctrl.Result{}, err
108+
}
109+
log.Info("pruned old child", "parent", snap.Name, "child", toDelete[i].Name)
86110
}
87-
if err := r.Delete(ctx, &toDelete[i]); client.IgnoreNotFound(err) != nil {
111+
// Re-list from API server to get an accurate post-prune view (accounts for
112+
// baseSnapshot children that were skipped during deletion).
113+
postPrune := &impv1alpha1.ImpVMSnapshotList{}
114+
if err := r.List(retCtx, postPrune,
115+
client.InNamespace(snap.Namespace),
116+
client.MatchingLabels{impv1alpha1.LabelSnapshotParent: snap.Name},
117+
); err != nil {
118+
tracing.RecordError(retSpan, err)
119+
retSpan.End()
88120
return ctrl.Result{}, err
89121
}
90-
log.Info("pruned old child", "parent", snap.Name, "child", toDelete[i].Name)
91-
}
92-
// Re-list from API server to get an accurate post-prune view (accounts for
93-
// baseSnapshot children that were skipped during deletion).
94-
postPrune := &impv1alpha1.ImpVMSnapshotList{}
95-
if err := r.List(ctx, postPrune,
96-
client.InNamespace(snap.Namespace),
97-
client.MatchingLabels{impv1alpha1.LabelSnapshotParent: snap.Name},
98-
); err != nil {
99-
return ctrl.Result{}, err
122+
children = postPrune.Items
100123
}
101-
children = postPrune.Items
124+
retSpan.End()
102125
}
103126

104127
// Design rule 6: BaseSnapshot validation.
@@ -143,8 +166,16 @@ func (r *ImpVMSnapshotReconciler) Reconcile(ctx context.Context, req ctrl.Reques
143166

144167
// If next tick is within 5s of now, create a child.
145168
if untilNext <= 5*time.Second {
146-
if err := r.createChild(ctx, snap); err != nil {
147-
return ctrl.Result{}, err
169+
cronCtx, cronSpan := otel.Tracer("imp.operator").Start(ctx, "operator.impvmsnapshot.cron_trigger",
170+
trace.WithAttributes(
171+
attribute.String("snap.name", req.Name),
172+
attribute.String("snap.namespace", req.Namespace),
173+
))
174+
cronErr := r.createChild(cronCtx, snap)
175+
tracing.RecordError(cronSpan, cronErr)
176+
cronSpan.End()
177+
if cronErr != nil {
178+
return ctrl.Result{}, cronErr
148179
}
149180
// Requeue after the next scheduled slot.
150181
now = time.Now()

0 commit comments

Comments
 (0)