@@ -55,6 +55,12 @@ func histogramSampleCount(histogram interface{ Write(*dto.Metric) error }) uint6
5555 return metric .GetHistogram ().GetSampleCount ()
5656}
5757
58+ func gaugeValue (gauge interface { Write (* dto.Metric ) error }) float64 {
59+ metric := & dto.Metric {}
60+ Expect (gauge .Write (metric )).To (Succeed ())
61+ return metric .GetGauge ().GetValue ()
62+ }
63+
5864// errorInjectingClient forces Patch to fail for selected nodes.
5965type errorInjectingClient struct {
6066 client.Client
@@ -2235,4 +2241,138 @@ var _ = Describe("NodeReadinessRule Controller", func() {
22352241 Expect (failedNames ).NotTo (ContainElement ("stale-recovery-node" ))
22362242 })
22372243 })
2244+
2245+ Context ("Metric: node_readiness_rule_matched_nodes" , func () {
2246+ It ("should set gauge to matched node count after reconcile" , func () {
2247+ ruleName := "sel-gauge-match-rule"
2248+ matchLabel := "sel-gauge-match"
2249+
2250+ nodeA := & corev1.Node {ObjectMeta : metav1.ObjectMeta {Name : "sel-gauge-node-a" , Labels : map [string ]string {matchLabel : "true" }}}
2251+ nodeB := & corev1.Node {ObjectMeta : metav1.ObjectMeta {Name : "sel-gauge-node-b" , Labels : map [string ]string {matchLabel : "true" }}}
2252+ nodeC := & corev1.Node {ObjectMeta : metav1.ObjectMeta {Name : "sel-gauge-node-c" , Labels : map [string ]string {"other" : "label" }}}
2253+ for _ , n := range []* corev1.Node {nodeA , nodeB , nodeC } {
2254+ Expect (k8sClient .Create (ctx , n )).To (Succeed ())
2255+ }
2256+ defer func () {
2257+ for _ , n := range []* corev1.Node {nodeA , nodeB , nodeC } {
2258+ _ = k8sClient .Delete (ctx , n )
2259+ }
2260+ }()
2261+
2262+ rule := & nodereadinessiov1alpha1.NodeReadinessRule {
2263+ ObjectMeta : metav1.ObjectMeta {Name : ruleName , Finalizers : []string {finalizerName }},
2264+ Spec : nodereadinessiov1alpha1.NodeReadinessRuleSpec {
2265+ Conditions : []nodereadinessiov1alpha1.ConditionRequirement {{Type : "Ready" , RequiredStatus : corev1 .ConditionTrue }},
2266+ Taint : corev1.Taint {Key : "readiness.k8s.io/sel-gauge-taint" , Effect : corev1 .TaintEffectNoSchedule },
2267+ NodeSelector : metav1.LabelSelector {MatchLabels : map [string ]string {matchLabel : "true" }},
2268+ EnforcementMode : nodereadinessiov1alpha1 .EnforcementModeContinuous ,
2269+ },
2270+ }
2271+ Expect (k8sClient .Create (ctx , rule )).To (Succeed ())
2272+ defer func () { _ = k8sClient .Delete (ctx , rule ) }()
2273+
2274+ _ , err := ruleReconciler .Reconcile (ctx , reconcile.Request {NamespacedName : types.NamespacedName {Name : ruleName }})
2275+ Expect (err ).NotTo (HaveOccurred ())
2276+
2277+ gauge , err := metrics .RuleMatchedNodes .GetMetricWith (prometheus.Labels {"rule" : ruleName })
2278+ Expect (err ).NotTo (HaveOccurred ())
2279+ Expect (gaugeValue (gauge )).To (Equal (2.0 ))
2280+ })
2281+
2282+ It ("should set gauge to 0 when no nodes match the selector" , func () {
2283+ ruleName := "sel-gauge-nomatch-rule"
2284+
2285+ rule := & nodereadinessiov1alpha1.NodeReadinessRule {
2286+ ObjectMeta : metav1.ObjectMeta {Name : ruleName , Finalizers : []string {finalizerName }},
2287+ Spec : nodereadinessiov1alpha1.NodeReadinessRuleSpec {
2288+ Conditions : []nodereadinessiov1alpha1.ConditionRequirement {{Type : "Ready" , RequiredStatus : corev1 .ConditionTrue }},
2289+ Taint : corev1.Taint {Key : "readiness.k8s.io/sel-nomatch-taint" , Effect : corev1 .TaintEffectNoSchedule },
2290+ NodeSelector : metav1.LabelSelector {MatchLabels : map [string ]string {"definitely-no-such-label" : "true" }},
2291+ EnforcementMode : nodereadinessiov1alpha1 .EnforcementModeContinuous ,
2292+ },
2293+ }
2294+ Expect (k8sClient .Create (ctx , rule )).To (Succeed ())
2295+ defer func () { _ = k8sClient .Delete (ctx , rule ) }()
2296+
2297+ _ , err := ruleReconciler .Reconcile (ctx , reconcile.Request {NamespacedName : types.NamespacedName {Name : ruleName }})
2298+ Expect (err ).NotTo (HaveOccurred ())
2299+
2300+ gauge , err := metrics .RuleMatchedNodes .GetMetricWith (prometheus.Labels {"rule" : ruleName })
2301+ Expect (err ).NotTo (HaveOccurred ())
2302+ Expect (gaugeValue (gauge )).To (Equal (0.0 ))
2303+ })
2304+
2305+ It ("should clean up label values on rule deletion" , func () {
2306+ ruleName := "sel-gauge-del-rule"
2307+ matchLabel := "sel-gauge-del"
2308+
2309+ node := & corev1.Node {ObjectMeta : metav1.ObjectMeta {Name : "sel-gauge-del-node" , Labels : map [string ]string {matchLabel : "true" }}}
2310+ Expect (k8sClient .Create (ctx , node )).To (Succeed ())
2311+ defer func () { _ = k8sClient .Delete (ctx , node ) }()
2312+
2313+ rule := & nodereadinessiov1alpha1.NodeReadinessRule {
2314+ ObjectMeta : metav1.ObjectMeta {Name : ruleName , Finalizers : []string {finalizerName }},
2315+ Spec : nodereadinessiov1alpha1.NodeReadinessRuleSpec {
2316+ Conditions : []nodereadinessiov1alpha1.ConditionRequirement {{Type : "Ready" , RequiredStatus : corev1 .ConditionTrue }},
2317+ Taint : corev1.Taint {Key : "readiness.k8s.io/sel-del-taint" , Effect : corev1 .TaintEffectNoSchedule },
2318+ NodeSelector : metav1.LabelSelector {MatchLabels : map [string ]string {matchLabel : "true" }},
2319+ EnforcementMode : nodereadinessiov1alpha1 .EnforcementModeContinuous ,
2320+ },
2321+ }
2322+ Expect (k8sClient .Create (ctx , rule )).To (Succeed ())
2323+
2324+ _ , err := ruleReconciler .Reconcile (ctx , reconcile.Request {NamespacedName : types.NamespacedName {Name : ruleName }})
2325+ Expect (err ).NotTo (HaveOccurred ())
2326+
2327+ gauge , err := metrics .RuleMatchedNodes .GetMetricWith (prometheus.Labels {"rule" : ruleName })
2328+ Expect (err ).NotTo (HaveOccurred ())
2329+ Expect (gaugeValue (gauge )).To (Equal (1.0 ), "gauge should be 1 before deletion" )
2330+
2331+ Expect (k8sClient .Delete (ctx , rule )).To (Succeed ())
2332+
2333+ // Deletion reconcile triggers reconcileDelete, which calls DeleteLabelValues.
2334+ Eventually (func () bool {
2335+ _ , err := ruleReconciler .Reconcile (ctx , reconcile.Request {NamespacedName : types.NamespacedName {Name : ruleName }})
2336+ Expect (err ).NotTo (HaveOccurred ())
2337+ readinessController .ruleCacheMutex .RLock ()
2338+ _ , exists := readinessController .ruleCache [ruleName ]
2339+ readinessController .ruleCacheMutex .RUnlock ()
2340+ return ! exists
2341+ }).Should (BeTrue ())
2342+
2343+ // After DeleteLabelValues, GetMetricWith allocates a fresh gauge at 0 (old value removed).
2344+ freshGauge , err := metrics .RuleMatchedNodes .GetMetricWith (prometheus.Labels {"rule" : ruleName })
2345+ Expect (err ).NotTo (HaveOccurred ())
2346+ Expect (gaugeValue (freshGauge )).To (Equal (0.0 ))
2347+ })
2348+ })
2349+
2350+ Context ("Metric: failures_total (reason=AnnotationPatchFailed)" , func () {
2351+ It ("should increment counter when annotation write fails" , func () {
2352+ ruleName := "bce-error-rule"
2353+ ruleUID := types .UID ("33333333-3333-3333-3333-333333333333" )
2354+
2355+ before := counterValue (metrics .Failures .WithLabelValues (ruleName , string (metrics .FailureReasonAnnotationPatchFailed )))
2356+
2357+ readinessController .markBootstrapCompleted (ctx , "nonexistent-node-for-bce-test" , ruleName , ruleUID )
2358+
2359+ Expect (counterValue (metrics .Failures .WithLabelValues (ruleName , string (metrics .FailureReasonAnnotationPatchFailed )))).To (Equal (before + 1 ))
2360+ })
2361+
2362+ It ("should not increment counter on successful annotation write" , func () {
2363+ nodeName := "bce-success-node"
2364+ ruleName := "bce-success-rule"
2365+ ruleUID := types .UID ("44444444-4444-4444-4444-444444444444" )
2366+
2367+ node := & corev1.Node {ObjectMeta : metav1.ObjectMeta {Name : nodeName }}
2368+ Expect (k8sClient .Create (ctx , node )).To (Succeed ())
2369+ defer func () { _ = k8sClient .Delete (ctx , node ) }()
2370+
2371+ before := counterValue (metrics .Failures .WithLabelValues (ruleName , string (metrics .FailureReasonAnnotationPatchFailed )))
2372+
2373+ readinessController .markBootstrapCompleted (ctx , nodeName , ruleName , ruleUID )
2374+
2375+ Expect (counterValue (metrics .Failures .WithLabelValues (ruleName , string (metrics .FailureReasonAnnotationPatchFailed )))).To (Equal (before ))
2376+ })
2377+ })
22382378})
0 commit comments