From 9095acc802634aa7e5067eca262f051e503cff77 Mon Sep 17 00:00:00 2001 From: Tingting Liu Date: Thu, 22 Jan 2026 03:38:31 +0000 Subject: [PATCH 1/8] Add metrics --- internal/controllers/scheduling/controller.go | 6 + internal/controllers/scheduling/metrics.go | 9 +- .../controllers/scheduling/metrics_test.go | 112 ++++++++++++++++++ 3 files changed, 126 insertions(+), 1 deletion(-) diff --git a/internal/controllers/scheduling/controller.go b/internal/controllers/scheduling/controller.go index 6d31d971..fc4d38d4 100644 --- a/internal/controllers/scheduling/controller.go +++ b/internal/controllers/scheduling/controller.go @@ -111,6 +111,9 @@ func (c *controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu nextSlot := c.getNextCooldownSlot(comps) logger.Info("listed compositions", "compositionCount", len(comps.Items), "nextCooldownSlot", nextSlot) + // Reset the compositionStatus metric before iterating through compositions + compositionStatus.Reset() + var inFlight int var op *op for _, comp := range comps.Items { @@ -122,7 +125,10 @@ func (c *controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu if missedReconciliation(&comp, c.watchdogThreshold) { synth := synthsByName[comp.Spec.Synthesizer.Name] stuckReconciling.WithLabelValues(comp.Spec.Synthesizer.Name, getSynthOwner(&synth)).Inc() + compositionStatus.WithLabelValues(comp.Name, comp.Namespace, comp.Spec.Synthesizer.Name).Set(1) logger.Info("detected composition missed reconciliation", "compositionName", comp.Name, "compositionNamespace", comp.Namespace, "synthesizerName", comp.Spec.Synthesizer.Name) + } else { + compositionStatus.WithLabelValues(comp.Name, comp.Namespace, comp.Spec.Synthesizer.Name).Set(0) } synth, ok := synthsByName[comp.Spec.Synthesizer.Name] diff --git a/internal/controllers/scheduling/metrics.go b/internal/controllers/scheduling/metrics.go index 480f43d5..b10fc82d 100644 --- a/internal/controllers/scheduling/metrics.go +++ b/internal/controllers/scheduling/metrics.go @@ -30,10 +30,17 @@ var ( Help: "Number of compositions that have not been reconciled since a period after their current synthesis was initialized", }, []string{"synthesizer", "owner"}, ) + + compositionStatus = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "eno_composition_status", + Help: "Status of composition reconciliation (1 = stuck, 0 = healthy)", + }, []string{"composition_name", "composition_namespace", "synthesizer_name"}, + ) ) func init() { - metrics.Registry.MustRegister(freeSynthesisSlots, schedulingLatency, stuckReconciling) + metrics.Registry.MustRegister(freeSynthesisSlots, schedulingLatency, stuckReconciling, compositionStatus) } func missedReconciliation(comp *apiv1.Composition, threshold time.Duration) bool { diff --git a/internal/controllers/scheduling/metrics_test.go b/internal/controllers/scheduling/metrics_test.go index 5c6d16a8..f79efabf 100644 --- a/internal/controllers/scheduling/metrics_test.go +++ b/internal/controllers/scheduling/metrics_test.go @@ -182,3 +182,115 @@ func TestMissedReconciliation(t *testing.T) { }) } } + +func TestCompositionStatusMetric(t *testing.T) { + tests := []struct { + name string + comp *apiv1.Composition + expectedStatus float64 + }{ + { + name: "Healthy composition - reconciled", + comp: &apiv1.Composition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "healthy-comp", + Namespace: "default", + }, + Spec: apiv1.CompositionSpec{ + Synthesizer: apiv1.SynthesizerRef{Name: "test-synth"}, + }, + Status: apiv1.CompositionStatus{ + CurrentSynthesis: &apiv1.Synthesis{ + Reconciled: &metav1.Time{Time: time.Now()}, + }, + }, + }, + expectedStatus: 0, + }, + { + name: "Stuck composition - missed reconciliation", + comp: &apiv1.Composition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "stuck-comp", + Namespace: "prod", + }, + Spec: apiv1.CompositionSpec{ + Synthesizer: apiv1.SynthesizerRef{Name: "prod-synth"}, + }, + Status: apiv1.CompositionStatus{ + CurrentSynthesis: &apiv1.Synthesis{ + Initialized: &metav1.Time{Time: time.Now().Add(-2 * time.Hour)}, + }, + }, + }, + expectedStatus: 1, + }, + { + name: "Healthy composition - within threshold", + comp: &apiv1.Composition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "recent-comp", + Namespace: "staging", + }, + Spec: apiv1.CompositionSpec{ + Synthesizer: apiv1.SynthesizerRef{Name: "staging-synth"}, + }, + Status: apiv1.CompositionStatus{ + CurrentSynthesis: &apiv1.Synthesis{ + Initialized: &metav1.Time{Time: time.Now().Add(-30 * time.Minute)}, + }, + }, + }, + expectedStatus: 0, + }, + { + name: "Stuck composition - stuck deleting", + comp: &apiv1.Composition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "deleting-comp", + Namespace: "default", + DeletionTimestamp: &metav1.Time{Time: time.Now().Add(-3 * time.Hour)}, + }, + Spec: apiv1.CompositionSpec{ + Synthesizer: apiv1.SynthesizerRef{Name: "test-synth"}, + }, + Status: apiv1.CompositionStatus{ + CurrentSynthesis: &apiv1.Synthesis{}, + }, + }, + expectedStatus: 1, + }, + { + name: "Stuck composition - no synthesis, old creation", + comp: &apiv1.Composition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "old-comp", + Namespace: "default", + CreationTimestamp: metav1.Time{Time: time.Now().Add(-2 * time.Hour)}, + }, + Spec: apiv1.CompositionSpec{ + Synthesizer: apiv1.SynthesizerRef{Name: "test-synth"}, + }, + Status: apiv1.CompositionStatus{ + CurrentSynthesis: nil, + }, + }, + expectedStatus: 1, + }, + } + + threshold := time.Hour + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + isStuck := missedReconciliation(tt.comp, threshold) + var status float64 + if isStuck { + status = 1 + } else { + status = 0 + } + assert.Equal(t, tt.expectedStatus, status, "composition %s/%s with synthesizer %s should have status %v", + tt.comp.Namespace, tt.comp.Name, tt.comp.Spec.Synthesizer.Name, tt.expectedStatus) + }) + } +} From 76a762bf339c364d031b6c8040c1cc1487cf8c9b Mon Sep 17 00:00:00 2001 From: Tingting Liu Date: Thu, 22 Jan 2026 16:32:38 +0000 Subject: [PATCH 2/8] fix ut --- .../controllers/scheduling/metrics_test.go | 111 ------------------ 1 file changed, 111 deletions(-) diff --git a/internal/controllers/scheduling/metrics_test.go b/internal/controllers/scheduling/metrics_test.go index f79efabf..3d6dd4dd 100644 --- a/internal/controllers/scheduling/metrics_test.go +++ b/internal/controllers/scheduling/metrics_test.go @@ -183,114 +183,3 @@ func TestMissedReconciliation(t *testing.T) { } } -func TestCompositionStatusMetric(t *testing.T) { - tests := []struct { - name string - comp *apiv1.Composition - expectedStatus float64 - }{ - { - name: "Healthy composition - reconciled", - comp: &apiv1.Composition{ - ObjectMeta: metav1.ObjectMeta{ - Name: "healthy-comp", - Namespace: "default", - }, - Spec: apiv1.CompositionSpec{ - Synthesizer: apiv1.SynthesizerRef{Name: "test-synth"}, - }, - Status: apiv1.CompositionStatus{ - CurrentSynthesis: &apiv1.Synthesis{ - Reconciled: &metav1.Time{Time: time.Now()}, - }, - }, - }, - expectedStatus: 0, - }, - { - name: "Stuck composition - missed reconciliation", - comp: &apiv1.Composition{ - ObjectMeta: metav1.ObjectMeta{ - Name: "stuck-comp", - Namespace: "prod", - }, - Spec: apiv1.CompositionSpec{ - Synthesizer: apiv1.SynthesizerRef{Name: "prod-synth"}, - }, - Status: apiv1.CompositionStatus{ - CurrentSynthesis: &apiv1.Synthesis{ - Initialized: &metav1.Time{Time: time.Now().Add(-2 * time.Hour)}, - }, - }, - }, - expectedStatus: 1, - }, - { - name: "Healthy composition - within threshold", - comp: &apiv1.Composition{ - ObjectMeta: metav1.ObjectMeta{ - Name: "recent-comp", - Namespace: "staging", - }, - Spec: apiv1.CompositionSpec{ - Synthesizer: apiv1.SynthesizerRef{Name: "staging-synth"}, - }, - Status: apiv1.CompositionStatus{ - CurrentSynthesis: &apiv1.Synthesis{ - Initialized: &metav1.Time{Time: time.Now().Add(-30 * time.Minute)}, - }, - }, - }, - expectedStatus: 0, - }, - { - name: "Stuck composition - stuck deleting", - comp: &apiv1.Composition{ - ObjectMeta: metav1.ObjectMeta{ - Name: "deleting-comp", - Namespace: "default", - DeletionTimestamp: &metav1.Time{Time: time.Now().Add(-3 * time.Hour)}, - }, - Spec: apiv1.CompositionSpec{ - Synthesizer: apiv1.SynthesizerRef{Name: "test-synth"}, - }, - Status: apiv1.CompositionStatus{ - CurrentSynthesis: &apiv1.Synthesis{}, - }, - }, - expectedStatus: 1, - }, - { - name: "Stuck composition - no synthesis, old creation", - comp: &apiv1.Composition{ - ObjectMeta: metav1.ObjectMeta{ - Name: "old-comp", - Namespace: "default", - CreationTimestamp: metav1.Time{Time: time.Now().Add(-2 * time.Hour)}, - }, - Spec: apiv1.CompositionSpec{ - Synthesizer: apiv1.SynthesizerRef{Name: "test-synth"}, - }, - Status: apiv1.CompositionStatus{ - CurrentSynthesis: nil, - }, - }, - expectedStatus: 1, - }, - } - - threshold := time.Hour - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - isStuck := missedReconciliation(tt.comp, threshold) - var status float64 - if isStuck { - status = 1 - } else { - status = 0 - } - assert.Equal(t, tt.expectedStatus, status, "composition %s/%s with synthesizer %s should have status %v", - tt.comp.Namespace, tt.comp.Name, tt.comp.Spec.Synthesizer.Name, tt.expectedStatus) - }) - } -} From fd30fd2af1700958222381518bb9d5198eb56a96 Mon Sep 17 00:00:00 2001 From: Tingting Liu Date: Thu, 22 Jan 2026 16:38:32 +0000 Subject: [PATCH 3/8] Rename --- internal/controllers/scheduling/controller.go | 8 ++++---- internal/controllers/scheduling/metrics.go | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/controllers/scheduling/controller.go b/internal/controllers/scheduling/controller.go index fc4d38d4..c58dc61c 100644 --- a/internal/controllers/scheduling/controller.go +++ b/internal/controllers/scheduling/controller.go @@ -111,8 +111,8 @@ func (c *controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu nextSlot := c.getNextCooldownSlot(comps) logger.Info("listed compositions", "compositionCount", len(comps.Items), "nextCooldownSlot", nextSlot) - // Reset the compositionStatus metric before iterating through compositions - compositionStatus.Reset() + // Reset the compositionHealth metric before iterating through compositions + compositionHealth.Reset() var inFlight int var op *op @@ -125,10 +125,10 @@ func (c *controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu if missedReconciliation(&comp, c.watchdogThreshold) { synth := synthsByName[comp.Spec.Synthesizer.Name] stuckReconciling.WithLabelValues(comp.Spec.Synthesizer.Name, getSynthOwner(&synth)).Inc() - compositionStatus.WithLabelValues(comp.Name, comp.Namespace, comp.Spec.Synthesizer.Name).Set(1) + compositionHealth.WithLabelValues(comp.Name, comp.Namespace, comp.Spec.Synthesizer.Name).Set(1) logger.Info("detected composition missed reconciliation", "compositionName", comp.Name, "compositionNamespace", comp.Namespace, "synthesizerName", comp.Spec.Synthesizer.Name) } else { - compositionStatus.WithLabelValues(comp.Name, comp.Namespace, comp.Spec.Synthesizer.Name).Set(0) + compositionHealth.WithLabelValues(comp.Name, comp.Namespace, comp.Spec.Synthesizer.Name).Set(0) } synth, ok := synthsByName[comp.Spec.Synthesizer.Name] diff --git a/internal/controllers/scheduling/metrics.go b/internal/controllers/scheduling/metrics.go index b10fc82d..b5832d73 100644 --- a/internal/controllers/scheduling/metrics.go +++ b/internal/controllers/scheduling/metrics.go @@ -31,16 +31,16 @@ var ( }, []string{"synthesizer", "owner"}, ) - compositionStatus = prometheus.NewGaugeVec( + compositionHealth = prometheus.NewGaugeVec( prometheus.GaugeOpts{ - Name: "eno_composition_status", - Help: "Status of composition reconciliation (1 = stuck, 0 = healthy)", + Name: "eno_composition_health", + Help: "Health status of each composition (0 = healthy, 1 = stuck/unhealthy)", }, []string{"composition_name", "composition_namespace", "synthesizer_name"}, ) ) func init() { - metrics.Registry.MustRegister(freeSynthesisSlots, schedulingLatency, stuckReconciling, compositionStatus) + metrics.Registry.MustRegister(freeSynthesisSlots, schedulingLatency, stuckReconciling, compositionHealth) } func missedReconciliation(comp *apiv1.Composition, threshold time.Duration) bool { From 5ee1e0e1a9e31f7b7b8672cb12e5a77c1281f487 Mon Sep 17 00:00:00 2001 From: Tingting Liu Date: Thu, 22 Jan 2026 16:39:09 +0000 Subject: [PATCH 4/8] fix ut --- internal/controllers/scheduling/metrics_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/controllers/scheduling/metrics_test.go b/internal/controllers/scheduling/metrics_test.go index 3d6dd4dd..5c6d16a8 100644 --- a/internal/controllers/scheduling/metrics_test.go +++ b/internal/controllers/scheduling/metrics_test.go @@ -182,4 +182,3 @@ func TestMissedReconciliation(t *testing.T) { }) } } - From b06eb3f07aa4c0b444c5a6fc5655d5055bf36f41 Mon Sep 17 00:00:00 2001 From: Tingting Liu Date: Thu, 22 Jan 2026 16:54:13 +0000 Subject: [PATCH 5/8] Add ut --- .../controllers/scheduling/metrics_test.go | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/internal/controllers/scheduling/metrics_test.go b/internal/controllers/scheduling/metrics_test.go index 5c6d16a8..aa06f96a 100644 --- a/internal/controllers/scheduling/metrics_test.go +++ b/internal/controllers/scheduling/metrics_test.go @@ -5,6 +5,7 @@ import ( "time" apiv1 "github.com/Azure/eno/api/v1" + "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -182,3 +183,73 @@ func TestMissedReconciliation(t *testing.T) { }) } } + +func TestCompositionHealthMetric(t *testing.T) { + // Reset metric before test + compositionHealth.Reset() + + tests := []struct { + name string + comp *apiv1.Composition + expectedValue float64 + }{ + { + name: "Healthy composition", + comp: &apiv1.Composition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "healthy-comp", + Namespace: "default", + }, + Spec: apiv1.CompositionSpec{ + Synthesizer: apiv1.SynthesizerRef{Name: "test-synth"}, + }, + Status: apiv1.CompositionStatus{ + CurrentSynthesis: &apiv1.Synthesis{ + Reconciled: &metav1.Time{Time: time.Now()}, + }, + }, + }, + expectedValue: 0, + }, + { + name: "Stuck composition", + comp: &apiv1.Composition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "stuck-comp", + Namespace: "prod", + }, + Spec: apiv1.CompositionSpec{ + Synthesizer: apiv1.SynthesizerRef{Name: "prod-synth"}, + }, + Status: apiv1.CompositionStatus{ + CurrentSynthesis: &apiv1.Synthesis{ + Initialized: &metav1.Time{Time: time.Now().Add(-2 * time.Hour)}, + }, + }, + }, + expectedValue: 1, + }, + } + + threshold := time.Hour + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Simulate what the controller does: set metric based on missedReconciliation + if missedReconciliation(tt.comp, threshold) { + compositionHealth.WithLabelValues(tt.comp.Name, tt.comp.Namespace, tt.comp.Spec.Synthesizer.Name).Set(1) + } else { + compositionHealth.WithLabelValues(tt.comp.Name, tt.comp.Namespace, tt.comp.Spec.Synthesizer.Name).Set(0) + } + + // Verify the metric value + value := testutil.ToFloat64(compositionHealth.WithLabelValues(tt.comp.Name, tt.comp.Namespace, tt.comp.Spec.Synthesizer.Name)) + assert.Equal(t, tt.expectedValue, value, "composition %s/%s should have health value %v", tt.comp.Namespace, tt.comp.Name, tt.expectedValue) + }) + } + + // Test that Reset() clears all metrics + compositionHealth.Reset() + // After reset, the metric should return 0 (default for non-existent gauge) + value := testutil.ToFloat64(compositionHealth.WithLabelValues("healthy-comp", "default", "test-synth")) + assert.Equal(t, float64(0), value, "metric should be 0 after reset") +} From 1bc575220e5b5cc05c65b4aac18af1ed11824e9d Mon Sep 17 00:00:00 2001 From: Tingting Liu Date: Thu, 22 Jan 2026 17:10:18 +0000 Subject: [PATCH 6/8] fix ut --- .../controllers/scheduling/controller_test.go | 53 ++++++++++++++ .../controllers/scheduling/metrics_test.go | 73 +------------------ 2 files changed, 54 insertions(+), 72 deletions(-) diff --git a/internal/controllers/scheduling/controller_test.go b/internal/controllers/scheduling/controller_test.go index ec3ce631..c2807dea 100644 --- a/internal/controllers/scheduling/controller_test.go +++ b/internal/controllers/scheduling/controller_test.go @@ -10,6 +10,7 @@ import ( apiv1 "github.com/Azure/eno/api/v1" "github.com/Azure/eno/internal/testutil" + prometheustestutil "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -715,3 +716,55 @@ func TestRetryContention(t *testing.T) { return err == nil && secondComp.Status.InFlightSynthesis != nil }) } + +// TestCompositionHealthMetrics proves that the compositionHealth metric is set correctly during reconciliation. +func TestCompositionHealthMetrics(t *testing.T) { + ctx := testutil.NewContext(t) + cli := testutil.NewClient(t) + + // Use a short watchdog threshold so we can test stuck detection + c := &controller{client: cli, concurrencyLimit: 10, watchdogThreshold: time.Millisecond * 100} + + synth := &apiv1.Synthesizer{} + synth.Name = "test-synth" + require.NoError(t, cli.Create(ctx, synth)) + + // Create a healthy composition (recently reconciled) + healthyComp := &apiv1.Composition{} + healthyComp.Name = "healthy-comp" + healthyComp.Namespace = "default" + healthyComp.Finalizers = []string{"eno.azure.io/cleanup"} + healthyComp.Spec.Synthesizer.Name = synth.Name + require.NoError(t, cli.Create(ctx, healthyComp)) + + healthyComp.Status.CurrentSynthesis = &apiv1.Synthesis{ + UUID: "healthy-uuid", + Reconciled: ptr.To(metav1.Now()), + } + require.NoError(t, cli.Status().Update(ctx, healthyComp)) + + // Create a stuck composition (initialized long ago, not reconciled) + stuckComp := &apiv1.Composition{} + stuckComp.Name = "stuck-comp" + stuckComp.Namespace = "default" + stuckComp.Finalizers = []string{"eno.azure.io/cleanup"} + stuckComp.Spec.Synthesizer.Name = synth.Name + require.NoError(t, cli.Create(ctx, stuckComp)) + + stuckComp.Status.CurrentSynthesis = &apiv1.Synthesis{ + UUID: "stuck-uuid", + Initialized: ptr.To(metav1.NewTime(time.Now().Add(-time.Hour))), // initialized long ago + } + require.NoError(t, cli.Status().Update(ctx, stuckComp)) + + // Run reconciliation + _, err := c.Reconcile(ctx, ctrl.Request{}) + require.NoError(t, err) + + // Verify metrics + healthyValue := prometheustestutil.ToFloat64(compositionHealth.WithLabelValues("healthy-comp", "default", "test-synth")) + assert.Equal(t, float64(0), healthyValue, "healthy composition should have health value 0") + + stuckValue := prometheustestutil.ToFloat64(compositionHealth.WithLabelValues("stuck-comp", "default", "test-synth")) + assert.Equal(t, float64(1), stuckValue, "stuck composition should have health value 1") +} diff --git a/internal/controllers/scheduling/metrics_test.go b/internal/controllers/scheduling/metrics_test.go index aa06f96a..01212cad 100644 --- a/internal/controllers/scheduling/metrics_test.go +++ b/internal/controllers/scheduling/metrics_test.go @@ -5,7 +5,6 @@ import ( "time" apiv1 "github.com/Azure/eno/api/v1" - "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -182,74 +181,4 @@ func TestMissedReconciliation(t *testing.T) { assert.Equal(t, tt.expected, result) }) } -} - -func TestCompositionHealthMetric(t *testing.T) { - // Reset metric before test - compositionHealth.Reset() - - tests := []struct { - name string - comp *apiv1.Composition - expectedValue float64 - }{ - { - name: "Healthy composition", - comp: &apiv1.Composition{ - ObjectMeta: metav1.ObjectMeta{ - Name: "healthy-comp", - Namespace: "default", - }, - Spec: apiv1.CompositionSpec{ - Synthesizer: apiv1.SynthesizerRef{Name: "test-synth"}, - }, - Status: apiv1.CompositionStatus{ - CurrentSynthesis: &apiv1.Synthesis{ - Reconciled: &metav1.Time{Time: time.Now()}, - }, - }, - }, - expectedValue: 0, - }, - { - name: "Stuck composition", - comp: &apiv1.Composition{ - ObjectMeta: metav1.ObjectMeta{ - Name: "stuck-comp", - Namespace: "prod", - }, - Spec: apiv1.CompositionSpec{ - Synthesizer: apiv1.SynthesizerRef{Name: "prod-synth"}, - }, - Status: apiv1.CompositionStatus{ - CurrentSynthesis: &apiv1.Synthesis{ - Initialized: &metav1.Time{Time: time.Now().Add(-2 * time.Hour)}, - }, - }, - }, - expectedValue: 1, - }, - } - - threshold := time.Hour - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Simulate what the controller does: set metric based on missedReconciliation - if missedReconciliation(tt.comp, threshold) { - compositionHealth.WithLabelValues(tt.comp.Name, tt.comp.Namespace, tt.comp.Spec.Synthesizer.Name).Set(1) - } else { - compositionHealth.WithLabelValues(tt.comp.Name, tt.comp.Namespace, tt.comp.Spec.Synthesizer.Name).Set(0) - } - - // Verify the metric value - value := testutil.ToFloat64(compositionHealth.WithLabelValues(tt.comp.Name, tt.comp.Namespace, tt.comp.Spec.Synthesizer.Name)) - assert.Equal(t, tt.expectedValue, value, "composition %s/%s should have health value %v", tt.comp.Namespace, tt.comp.Name, tt.expectedValue) - }) - } - - // Test that Reset() clears all metrics - compositionHealth.Reset() - // After reset, the metric should return 0 (default for non-existent gauge) - value := testutil.ToFloat64(compositionHealth.WithLabelValues("healthy-comp", "default", "test-synth")) - assert.Equal(t, float64(0), value, "metric should be 0 after reset") -} +} \ No newline at end of file From 38b5399a0cd3d915a57519cda5c136da5b38a054 Mon Sep 17 00:00:00 2001 From: Tingting Liu Date: Thu, 22 Jan 2026 17:12:54 +0000 Subject: [PATCH 7/8] fix ut --- internal/controllers/scheduling/metrics_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/controllers/scheduling/metrics_test.go b/internal/controllers/scheduling/metrics_test.go index 01212cad..5c6d16a8 100644 --- a/internal/controllers/scheduling/metrics_test.go +++ b/internal/controllers/scheduling/metrics_test.go @@ -181,4 +181,4 @@ func TestMissedReconciliation(t *testing.T) { assert.Equal(t, tt.expected, result) }) } -} \ No newline at end of file +} From 023ff28373236eaab7695d97563d477829978163 Mon Sep 17 00:00:00 2001 From: Tingting Liu Date: Thu, 22 Jan 2026 17:16:04 +0000 Subject: [PATCH 8/8] fix ut --- internal/controllers/scheduling/controller_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/controllers/scheduling/controller_test.go b/internal/controllers/scheduling/controller_test.go index c2807dea..8eae71b7 100644 --- a/internal/controllers/scheduling/controller_test.go +++ b/internal/controllers/scheduling/controller_test.go @@ -722,7 +722,6 @@ func TestCompositionHealthMetrics(t *testing.T) { ctx := testutil.NewContext(t) cli := testutil.NewClient(t) - // Use a short watchdog threshold so we can test stuck detection c := &controller{client: cli, concurrencyLimit: 10, watchdogThreshold: time.Millisecond * 100} synth := &apiv1.Synthesizer{}