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
2933var 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