@@ -55,6 +55,12 @@ func histogramSampleCount(histogram interface{ Write(*dto.Metric) error }) uint6
5555 return metric .GetHistogram ().GetSampleCount ()
5656}
5757
58+ func histogramSampleSum (histogram interface { Write (* dto.Metric ) error }) float64 {
59+ metric := & dto.Metric {}
60+ Expect (histogram .Write (metric )).To (Succeed ())
61+ return metric .GetHistogram ().GetSampleSum ()
62+ }
63+
5864// errorInjectingClient forces Patch to fail for selected nodes.
5965type errorInjectingClient struct {
6066 client.Client
@@ -2327,7 +2333,7 @@ var _ = Describe("NodeReadinessRule Controller", func() {
23272333
23282334 Expect (k8sClient .Get (ctx , types.NamespacedName {Name : nodeName }, node )).To (Succeed ())
23292335 node .Status .Conditions = []corev1.NodeCondition {
2330- {Type : "Ready" , Status : corev1 .ConditionTrue },
2336+ {Type : "Ready" , Status : corev1 .ConditionTrue , LastTransitionTime : metav1 . NewTime ( time . Now (). Add ( 2 * time . Second )) },
23312337 }
23322338 Expect (k8sClient .Status ().Update (ctx , node )).To (Succeed ())
23332339 Expect (k8sClient .Get (ctx , types.NamespacedName {Name : nodeName }, node )).To (Succeed ())
@@ -2369,7 +2375,7 @@ var _ = Describe("NodeReadinessRule Controller", func() {
23692375
23702376 Expect (k8sClient .Get (ctx , types.NamespacedName {Name : nodeName }, node )).To (Succeed ())
23712377 node .Status .Conditions = []corev1.NodeCondition {
2372- {Type : "Ready" , Status : corev1 .ConditionTrue },
2378+ {Type : "Ready" , Status : corev1 .ConditionTrue , LastTransitionTime : metav1 . NewTime ( time . Now (). Add ( 2 * time . Second )) },
23732379 }
23742380 Expect (k8sClient .Status ().Update (ctx , node )).To (Succeed ())
23752381 Expect (k8sClient .Get (ctx , types.NamespacedName {Name : nodeName }, node )).To (Succeed ())
@@ -2484,6 +2490,105 @@ var _ = Describe("NodeReadinessRule Controller", func() {
24842490 Expect (taintAppliedAtIsZero (rule , nodeName )).To (BeTrue ())
24852491 })
24862492
2493+ It ("should use LastTransitionTime to calculate BootstrapNRCDuration" , func () {
2494+ ruleName := "nrc-dur-clock-anchor-rule"
2495+ nodeName := "nrc-dur-clock-anchor-node"
2496+ labelKey := "nrc-dur-clock-anchor"
2497+
2498+ rule := newBootstrapOnlyRule (ruleName , labelKey )
2499+ Expect (k8sClient .Create (ctx , rule )).To (Succeed ())
2500+ defer func () { _ = k8sClient .Delete (ctx , rule ) }()
2501+
2502+ // Node without taint, condition NOT satisfied.
2503+ node := & corev1.Node {
2504+ ObjectMeta : metav1.ObjectMeta {
2505+ Name : nodeName ,
2506+ Labels : map [string ]string {labelKey : "true" },
2507+ },
2508+ Status : corev1.NodeStatus {
2509+ Conditions : []corev1.NodeCondition {
2510+ {Type : "Ready" , Status : corev1 .ConditionFalse },
2511+ },
2512+ },
2513+ }
2514+ Expect (k8sClient .Create (ctx , node )).To (Succeed ())
2515+ defer func () { _ = k8sClient .Delete (ctx , node ) }()
2516+
2517+ Expect (k8sClient .Get (ctx , types.NamespacedName {Name : ruleName }, rule )).To (Succeed ())
2518+ Expect (readinessController .evaluateRuleForNode (ctx , rule , node )).To (Succeed ())
2519+ Expect (taintAppliedAtIsZero (rule , nodeName )).To (BeFalse ())
2520+
2521+ // Backdate TaintAppliedAt to verify the duration uses LastTransitionTime
2522+ backdated := metav1 .NewTime (time .Now ().Add (- 1 * time .Hour ))
2523+ for i := range rule .Status .NodeEvaluations {
2524+ if rule .Status .NodeEvaluations [i ].NodeName == nodeName {
2525+ rule .Status .NodeEvaluations [i ].TaintAppliedAt = backdated
2526+ }
2527+ }
2528+
2529+ transitionTime := metav1 .NewTime (backdated .Add (2 * time .Second ))
2530+ Expect (k8sClient .Get (ctx , types.NamespacedName {Name : nodeName }, node )).To (Succeed ())
2531+ node .Status .Conditions = []corev1.NodeCondition {
2532+ {Type : "Ready" , Status : corev1 .ConditionTrue , LastTransitionTime : transitionTime },
2533+ }
2534+ Expect (k8sClient .Status ().Update (ctx , node )).To (Succeed ())
2535+ Expect (k8sClient .Get (ctx , types.NamespacedName {Name : nodeName }, node )).To (Succeed ())
2536+
2537+ histogram := metrics .BootstrapNRCDuration .WithLabelValues (ruleName ).(prometheus.Histogram )
2538+ sumBefore := histogramSampleSum (histogram )
2539+ countBefore := histogramSampleCount (histogram )
2540+
2541+ Expect (readinessController .evaluateRuleForNode (ctx , rule , node )).To (Succeed ())
2542+
2543+ Expect (histogramSampleCount (histogram )).To (Equal (countBefore + 1 ))
2544+ observed := histogramSampleSum (histogram ) - sumBefore
2545+
2546+ Expect (observed ).To (BeNumerically (">=" , 0 ))
2547+ Expect (observed ).To (BeNumerically ("<" , 10 ))
2548+ })
2549+
2550+ It ("should skip BootstrapNRCDuration when LastTransitionTime is unset" , func () {
2551+ ruleName := "nrc-dur-zero-transition-rule"
2552+ nodeName := "nrc-dur-zero-transition-node"
2553+ labelKey := "nrc-dur-zero-transition"
2554+
2555+ rule := newBootstrapOnlyRule (ruleName , labelKey )
2556+ Expect (k8sClient .Create (ctx , rule )).To (Succeed ())
2557+ defer func () { _ = k8sClient .Delete (ctx , rule ) }()
2558+
2559+ node := & corev1.Node {
2560+ ObjectMeta : metav1.ObjectMeta {
2561+ Name : nodeName ,
2562+ Labels : map [string ]string {labelKey : "true" },
2563+ },
2564+ Status : corev1.NodeStatus {
2565+ Conditions : []corev1.NodeCondition {
2566+ {Type : "Ready" , Status : corev1 .ConditionFalse },
2567+ },
2568+ },
2569+ }
2570+ Expect (k8sClient .Create (ctx , node )).To (Succeed ())
2571+ defer func () { _ = k8sClient .Delete (ctx , node ) }()
2572+
2573+ Expect (k8sClient .Get (ctx , types.NamespacedName {Name : ruleName }, rule )).To (Succeed ())
2574+ Expect (readinessController .evaluateRuleForNode (ctx , rule , node )).To (Succeed ())
2575+ Expect (taintAppliedAtIsZero (rule , nodeName )).To (BeFalse ())
2576+
2577+ Expect (k8sClient .Get (ctx , types.NamespacedName {Name : nodeName }, node )).To (Succeed ())
2578+ node .Status .Conditions = []corev1.NodeCondition {
2579+ {Type : "Ready" , Status : corev1 .ConditionTrue },
2580+ }
2581+ Expect (k8sClient .Status ().Update (ctx , node )).To (Succeed ())
2582+ Expect (k8sClient .Get (ctx , types.NamespacedName {Name : nodeName }, node )).To (Succeed ())
2583+
2584+ histogram := metrics .BootstrapNRCDuration .WithLabelValues (ruleName ).(prometheus.Histogram )
2585+ before := histogramSampleCount (histogram )
2586+
2587+ Expect (readinessController .evaluateRuleForNode (ctx , rule , node )).To (Succeed ())
2588+
2589+ Expect (histogramSampleCount (histogram )).To (Equal (before ))
2590+ })
2591+
24872592 It ("should clean up BootstrapNRCDuration label values on rule deletion" , func () {
24882593 ruleName := "nrc-dur-del-rule"
24892594 labelKey := "nrc-dur-del"
0 commit comments