@@ -22,8 +22,11 @@ import (
2222 "strconv"
2323 "time"
2424
25+ "github.com/google/go-cmp/cmp"
2526 . "github.com/onsi/ginkgo/v2"
2627 . "github.com/onsi/gomega"
28+ "github.com/prometheus/client_golang/prometheus"
29+ prometheusclientmodel "github.com/prometheus/client_model/go"
2730
2831 rbacv1 "k8s.io/api/rbac/v1"
2932 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@@ -32,6 +35,7 @@ import (
3235 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3336 "k8s.io/apimachinery/pkg/runtime"
3437 "k8s.io/apimachinery/pkg/types"
38+ "k8s.io/utils/ptr"
3539 "sigs.k8s.io/controller-runtime/pkg/client"
3640 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3741
@@ -40,6 +44,8 @@ import (
4044 placementv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1"
4145 "github.com/kubefleet-dev/kubefleet/pkg/utils"
4246 "github.com/kubefleet-dev/kubefleet/pkg/utils/condition"
47+ "github.com/kubefleet-dev/kubefleet/pkg/utils/controller/metrics"
48+ metricsutils "github.com/kubefleet-dev/kubefleet/test/utils/metrics"
4349)
4450
4551const (
6975 testCROName string
7076 updateRunNamespacedName types.NamespacedName
7177 testNamespace []byte
78+ customRegistry * prometheus.Registry
7279)
7380
7481var _ = Describe ("Test the clusterStagedUpdateRun controller" , func () {
@@ -80,6 +87,15 @@ var _ = Describe("Test the clusterStagedUpdateRun controller", func() {
8087 testUpdateStrategyName = "updatestrategy-" + utils .RandStr ()
8188 testCROName = "cro-" + utils .RandStr ()
8289 updateRunNamespacedName = types.NamespacedName {Name : testUpdateRunName }
90+
91+ customRegistry = initializeUpdateRunMetricsRegistry ()
92+ })
93+
94+ AfterEach (func () {
95+ By ("Checking the update run status metrics are removed" )
96+ // No metrics are emitted as all are removed after updateRun is deleted.
97+ validateUpdateRunMetricsEmitted (customRegistry )
98+ unregisterUpdateRunMetrics (customRegistry )
8399 })
84100
85101 Context ("Test reconciling a clusterStagedUpdateRun" , func () {
@@ -223,6 +239,114 @@ var _ = Describe("Test the clusterStagedUpdateRun controller", func() {
223239 })
224240})
225241
242+ func initializeUpdateRunMetricsRegistry () * prometheus.Registry {
243+ // Create a test registry
244+ customRegistry := prometheus .NewRegistry ()
245+ Expect (customRegistry .Register (metrics .FleetUpdateRunStatusLastTimestampSeconds )).Should (Succeed ())
246+ // Reset metrics before each test
247+ metrics .FleetUpdateRunStatusLastTimestampSeconds .Reset ()
248+ return customRegistry
249+ }
250+
251+ func unregisterUpdateRunMetrics (registry * prometheus.Registry ) {
252+ Expect (registry .Unregister (metrics .FleetUpdateRunStatusLastTimestampSeconds )).Should (BeTrue ())
253+ }
254+
255+ // validateUpdateRunMetricsEmitted validates the update run status metrics are emitted and are emitted in the correct order.
256+ func validateUpdateRunMetricsEmitted (registry * prometheus.Registry , wantMetrics ... * prometheusclientmodel.Metric ) {
257+ Eventually (func () error {
258+ metricFamilies , err := registry .Gather ()
259+ if err != nil {
260+ return fmt .Errorf ("failed to gather metrics: %w" , err )
261+ }
262+ var gotMetrics []* prometheusclientmodel.Metric
263+ for _ , mf := range metricFamilies {
264+ if mf .GetName () == "fleet_workload_update_run_status_last_timestamp_seconds" {
265+ gotMetrics = mf .GetMetric ()
266+ }
267+ }
268+
269+ if diff := cmp .Diff (gotMetrics , wantMetrics , metricsutils .MetricsCmpOptions ... ); diff != "" {
270+ return fmt .Errorf ("update run status metrics mismatch (-got, +want):\n %s" , diff )
271+ }
272+
273+ return nil
274+ }, timeout , interval ).Should (Succeed (), "failed to validate the update run status metrics" )
275+ }
276+
277+ func generateMetricsLabels (
278+ updateRun * placementv1beta1.ClusterStagedUpdateRun ,
279+ condition , status , reason string ,
280+ ) []* prometheusclientmodel.LabelPair {
281+ return []* prometheusclientmodel.LabelPair {
282+ {Name : ptr .To ("name" ), Value : & updateRun .Name },
283+ {Name : ptr .To ("generation" ), Value : ptr .To (strconv .FormatInt (updateRun .Generation , 10 ))},
284+ {Name : ptr .To ("condition" ), Value : ptr .To (condition )},
285+ {Name : ptr .To ("status" ), Value : ptr .To (status )},
286+ {Name : ptr .To ("reason" ), Value : ptr .To (reason )},
287+ }
288+ }
289+
290+ func generateInitializationFailedMetric (updateRun * placementv1beta1.ClusterStagedUpdateRun ) * prometheusclientmodel.Metric {
291+ return & prometheusclientmodel.Metric {
292+ Label : generateMetricsLabels (updateRun , string (placementv1beta1 .StagedUpdateRunConditionInitialized ),
293+ string (metav1 .ConditionFalse ), condition .UpdateRunInitializeFailedReason ),
294+ Gauge : & prometheusclientmodel.Gauge {
295+ Value : ptr .To (float64 (time .Now ().UnixNano ()) / 1e9 ),
296+ },
297+ }
298+ }
299+
300+ func generateProgressingMetric (updateRun * placementv1beta1.ClusterStagedUpdateRun ) * prometheusclientmodel.Metric {
301+ return & prometheusclientmodel.Metric {
302+ Label : generateMetricsLabels (updateRun , string (placementv1beta1 .StagedUpdateRunConditionProgressing ),
303+ string (metav1 .ConditionTrue ), condition .UpdateRunProgressingReason ),
304+ Gauge : & prometheusclientmodel.Gauge {
305+ Value : ptr .To (float64 (time .Now ().UnixNano ()) / 1e9 ),
306+ },
307+ }
308+ }
309+
310+ func generateWaitingMetric (updateRun * placementv1beta1.ClusterStagedUpdateRun ) * prometheusclientmodel.Metric {
311+ return & prometheusclientmodel.Metric {
312+ Label : generateMetricsLabels (updateRun , string (placementv1beta1 .StagedUpdateRunConditionProgressing ),
313+ string (metav1 .ConditionFalse ), condition .UpdateRunWaitingReason ),
314+ Gauge : & prometheusclientmodel.Gauge {
315+ Value : ptr .To (float64 (time .Now ().UnixNano ()) / 1e9 ),
316+ },
317+ }
318+ }
319+
320+ func generateStuckMetric (updateRun * placementv1beta1.ClusterStagedUpdateRun ) * prometheusclientmodel.Metric {
321+ return & prometheusclientmodel.Metric {
322+ Label : generateMetricsLabels (updateRun , string (placementv1beta1 .StagedUpdateRunConditionProgressing ),
323+ string (metav1 .ConditionFalse ), condition .UpdateRunStuckReason ),
324+ Gauge : & prometheusclientmodel.Gauge {
325+ Value : ptr .To (float64 (time .Now ().UnixNano ()) / 1e9 ),
326+ },
327+ }
328+ }
329+
330+ func generateFailedMetric (updateRun * placementv1beta1.ClusterStagedUpdateRun ) * prometheusclientmodel.Metric {
331+ return & prometheusclientmodel.Metric {
332+ Label : generateMetricsLabels (updateRun , string (placementv1beta1 .StagedUpdateRunConditionSucceeded ),
333+ string (metav1 .ConditionFalse ), condition .UpdateRunFailedReason ),
334+ Gauge : & prometheusclientmodel.Gauge {
335+ Value : ptr .To (float64 (time .Now ().UnixNano ()) / 1e9 ),
336+ },
337+ }
338+ }
339+
340+ func generateSucceededMetric (updateRun * placementv1beta1.ClusterStagedUpdateRun ) * prometheusclientmodel.Metric {
341+ return & prometheusclientmodel.Metric {
342+ Label : generateMetricsLabels (updateRun , string (placementv1beta1 .StagedUpdateRunConditionSucceeded ),
343+ string (metav1 .ConditionTrue ), condition .UpdateRunSucceededReason ),
344+ Gauge : & prometheusclientmodel.Gauge {
345+ Value : ptr .To (float64 (time .Now ().UnixNano ()) / 1e9 ),
346+ },
347+ }
348+ }
349+
226350func generateTestClusterStagedUpdateRun () * placementv1beta1.ClusterStagedUpdateRun {
227351 return & placementv1beta1.ClusterStagedUpdateRun {
228352 ObjectMeta : metav1.ObjectMeta {
@@ -503,7 +627,7 @@ func generateTrueCondition(obj client.Object, condType any) metav1.Condition {
503627 case placementv1beta1 .StagedUpdateRunConditionInitialized :
504628 reason = condition .UpdateRunInitializeSucceededReason
505629 case placementv1beta1 .StagedUpdateRunConditionProgressing :
506- reason = condition .UpdateRunStartedReason
630+ reason = condition .UpdateRunProgressingReason
507631 case placementv1beta1 .StagedUpdateRunConditionSucceeded :
508632 reason = condition .UpdateRunSucceededReason
509633 }
@@ -564,6 +688,8 @@ func generateFalseCondition(obj client.Object, condType any) metav1.Condition {
564688 reason = condition .UpdateRunInitializeFailedReason
565689 case placementv1beta1 .StagedUpdateRunConditionSucceeded :
566690 reason = condition .UpdateRunFailedReason
691+ case placementv1beta1 .StagedUpdateRunConditionProgressing :
692+ reason = condition .UpdateRunWaitingReason
567693 }
568694 typeStr = string (cond )
569695 case placementv1beta1.StageUpdatingConditionType :
0 commit comments