@@ -2178,7 +2178,7 @@ var _ = Describe("NodeReadinessRule Controller", func() {
21782178 Expect (failedNames ).To (ContainElement ("fail-path-node" ))
21792179 })
21802180
2181- It ("should remove stale failedNodes entry when evaluation succeeds and include the node in appliedNodes " , func () {
2181+ It ("stale failedNodes entries are cleared after successful evaluation " , func () {
21822182 successNode := & corev1.Node {
21832183 ObjectMeta : metav1.ObjectMeta {
21842184 Name : "stale-recovery-node" ,
@@ -2235,4 +2235,158 @@ var _ = Describe("NodeReadinessRule Controller", func() {
22352235 Expect (failedNames ).NotTo (ContainElement ("stale-recovery-node" ))
22362236 })
22372237 })
2238+
2239+ Context ("ConditionPolicy" , func () {
2240+ var (
2241+ anyOfController * RuleReadinessController
2242+ anyOfNode * corev1.Node
2243+ )
2244+
2245+ BeforeEach (func () {
2246+ anyOfController = & RuleReadinessController {
2247+ Client : k8sClient ,
2248+ Scheme : scheme ,
2249+ clientset : fakeClientset ,
2250+ ruleCache : make (map [string ]* nodereadinessiov1alpha1.NodeReadinessRule ),
2251+ EventRecorder : record .NewFakeRecorder (10 ),
2252+ }
2253+ anyOfNode = & corev1.Node {
2254+ ObjectMeta : metav1.ObjectMeta {
2255+ Name : "anyof-test-node" ,
2256+ Labels : map [string ]string {"anyof-test" : "true" },
2257+ },
2258+ Spec : corev1.NodeSpec {},
2259+ Status : corev1.NodeStatus {},
2260+ }
2261+ })
2262+
2263+ It ("anyOf: removes taint when at least one condition is satisfied" , func () {
2264+ anyOfNode .Status .Conditions = []corev1.NodeCondition {
2265+ {Type : "gpu.example.com/HardwareDriverReady" , Status : corev1 .ConditionTrue },
2266+ {Type : "gpu.example.com/SoftwareFallbackReady" , Status : corev1 .ConditionFalse },
2267+ }
2268+ anyOfNode .Spec .Taints = []corev1.Taint {
2269+ {Key : "readiness.k8s.io/GPUReady" , Effect : corev1 .TaintEffectNoSchedule },
2270+ }
2271+
2272+ rule := & nodereadinessiov1alpha1.NodeReadinessRule {
2273+ ObjectMeta : metav1.ObjectMeta {Name : "anyof-rule-removes-taint" },
2274+ Spec : nodereadinessiov1alpha1.NodeReadinessRuleSpec {
2275+ ConditionPolicy : nodereadinessiov1alpha1 .ConditionPolicyAnyOf ,
2276+ Conditions : []nodereadinessiov1alpha1.ConditionRequirement {
2277+ {Type : "gpu.example.com/HardwareDriverReady" , RequiredStatus : corev1 .ConditionTrue },
2278+ {Type : "gpu.example.com/SoftwareFallbackReady" , RequiredStatus : corev1 .ConditionTrue },
2279+ },
2280+ Taint : corev1.Taint {Key : "readiness.k8s.io/GPUReady" , Effect : corev1 .TaintEffectNoSchedule },
2281+ NodeSelector : metav1.LabelSelector {MatchLabels : map [string ]string {"anyof-test" : "true" }},
2282+ EnforcementMode : nodereadinessiov1alpha1 .EnforcementModeContinuous ,
2283+ },
2284+ }
2285+ anyOfController .updateRuleCache (ctx , rule )
2286+
2287+ Expect (k8sClient .Create (ctx , anyOfNode )).To (Succeed ())
2288+ defer func () { Expect (k8sClient .Delete (ctx , anyOfNode )).To (Succeed ()) }()
2289+ Expect (anyOfController .evaluateRuleForNode (ctx , rule , anyOfNode )).To (Succeed ())
2290+
2291+ // Taint should have been removed because HardwareDriverReady=True satisfies anyOf
2292+ Expect (anyOfController .hasTaintBySpec (anyOfNode , rule .Spec .Taint )).To (BeFalse ())
2293+
2294+ // conditionResults in status should reflect actual observed values
2295+ eval := anyOfController .getPreviousNodeEvaluation (rule , anyOfNode .Name )
2296+ Expect (eval ).NotTo (BeNil ())
2297+ Expect (eval .ConditionResults ).To (HaveLen (2 ))
2298+ })
2299+
2300+ It ("anyOf: keeps taint when no conditions are satisfied" , func () {
2301+ anyOfNode .Status .Conditions = []corev1.NodeCondition {
2302+ {Type : "gpu.example.com/HardwareDriverReady" , Status : corev1 .ConditionFalse },
2303+ {Type : "gpu.example.com/SoftwareFallbackReady" , Status : corev1 .ConditionFalse },
2304+ }
2305+ // Node has no taint; controller should add one
2306+ anyOfNode .Spec .Taints = nil
2307+
2308+ rule := & nodereadinessiov1alpha1.NodeReadinessRule {
2309+ ObjectMeta : metav1.ObjectMeta {Name : "anyof-rule-adds-taint" },
2310+ Spec : nodereadinessiov1alpha1.NodeReadinessRuleSpec {
2311+ ConditionPolicy : nodereadinessiov1alpha1 .ConditionPolicyAnyOf ,
2312+ Conditions : []nodereadinessiov1alpha1.ConditionRequirement {
2313+ {Type : "gpu.example.com/HardwareDriverReady" , RequiredStatus : corev1 .ConditionTrue },
2314+ {Type : "gpu.example.com/SoftwareFallbackReady" , RequiredStatus : corev1 .ConditionTrue },
2315+ },
2316+ Taint : corev1.Taint {Key : "readiness.k8s.io/GPUReady" , Effect : corev1 .TaintEffectNoSchedule },
2317+ NodeSelector : metav1.LabelSelector {MatchLabels : map [string ]string {"anyof-test" : "true" }},
2318+ EnforcementMode : nodereadinessiov1alpha1 .EnforcementModeContinuous ,
2319+ },
2320+ }
2321+ anyOfController .updateRuleCache (ctx , rule )
2322+
2323+ Expect (k8sClient .Create (ctx , anyOfNode )).To (Succeed ())
2324+ defer func () { Expect (k8sClient .Delete (ctx , anyOfNode )).To (Succeed ()) }()
2325+ Expect (anyOfController .evaluateRuleForNode (ctx , rule , anyOfNode )).To (Succeed ())
2326+
2327+ // Taint should have been added because neither condition is satisfied
2328+ Expect (anyOfController .hasTaintBySpec (anyOfNode , rule .Spec .Taint )).To (BeTrue ())
2329+ })
2330+
2331+ It ("allOf (explicit): keeps taint when only one of two conditions is satisfied" , func () {
2332+ anyOfNode .Status .Conditions = []corev1.NodeCondition {
2333+ {Type : "example.com/CondA" , Status : corev1 .ConditionTrue },
2334+ {Type : "example.com/CondB" , Status : corev1 .ConditionFalse },
2335+ }
2336+ anyOfNode .Spec .Taints = []corev1.Taint {
2337+ {Key : "readiness.k8s.io/MultiCond" , Effect : corev1 .TaintEffectNoSchedule },
2338+ }
2339+
2340+ rule := & nodereadinessiov1alpha1.NodeReadinessRule {
2341+ ObjectMeta : metav1.ObjectMeta {Name : "allof-explicit-rule" },
2342+ Spec : nodereadinessiov1alpha1.NodeReadinessRuleSpec {
2343+ ConditionPolicy : nodereadinessiov1alpha1 .ConditionPolicyAllOf ,
2344+ Conditions : []nodereadinessiov1alpha1.ConditionRequirement {
2345+ {Type : "example.com/CondA" , RequiredStatus : corev1 .ConditionTrue },
2346+ {Type : "example.com/CondB" , RequiredStatus : corev1 .ConditionTrue },
2347+ },
2348+ Taint : corev1.Taint {Key : "readiness.k8s.io/MultiCond" , Effect : corev1 .TaintEffectNoSchedule },
2349+ NodeSelector : metav1.LabelSelector {MatchLabels : map [string ]string {"anyof-test" : "true" }},
2350+ EnforcementMode : nodereadinessiov1alpha1 .EnforcementModeContinuous ,
2351+ },
2352+ }
2353+ anyOfController .updateRuleCache (ctx , rule )
2354+
2355+ Expect (k8sClient .Create (ctx , anyOfNode )).To (Succeed ())
2356+ defer func () { Expect (k8sClient .Delete (ctx , anyOfNode )).To (Succeed ()) }()
2357+ Expect (anyOfController .evaluateRuleForNode (ctx , rule , anyOfNode )).To (Succeed ())
2358+
2359+ // Taint must remain because CondB is still False
2360+ Expect (anyOfController .hasTaintBySpec (anyOfNode , rule .Spec .Taint )).To (BeTrue ())
2361+ })
2362+
2363+ It ("anyOf: missing condition evaluated via defaultStatus does not auto-satisfy" , func () {
2364+ // Node has NO conditions at all
2365+ anyOfNode .Status .Conditions = nil
2366+ anyOfNode .Spec .Taints = nil
2367+
2368+ rule := & nodereadinessiov1alpha1.NodeReadinessRule {
2369+ ObjectMeta : metav1.ObjectMeta {Name : "anyof-missing-condition-rule" },
2370+ Spec : nodereadinessiov1alpha1.NodeReadinessRuleSpec {
2371+ ConditionPolicy : nodereadinessiov1alpha1 .ConditionPolicyAnyOf ,
2372+ Conditions : []nodereadinessiov1alpha1.ConditionRequirement {
2373+ // No defaultStatus set — will resolve to Unknown, which != True
2374+ {Type : "gpu.example.com/HardwareDriverReady" , RequiredStatus : corev1 .ConditionTrue },
2375+ },
2376+ Taint : corev1.Taint {Key : "readiness.k8s.io/GPUReady" , Effect : corev1 .TaintEffectNoSchedule },
2377+ NodeSelector : metav1.LabelSelector {MatchLabels : map [string ]string {"anyof-test" : "true" }},
2378+ EnforcementMode : nodereadinessiov1alpha1 .EnforcementModeContinuous ,
2379+ },
2380+ }
2381+ anyOfController .updateRuleCache (ctx , rule )
2382+
2383+ Expect (k8sClient .Create (ctx , anyOfNode )).To (Succeed ())
2384+ defer func () { Expect (k8sClient .Delete (ctx , anyOfNode )).To (Succeed ()) }()
2385+ Expect (anyOfController .evaluateRuleForNode (ctx , rule , anyOfNode )).To (Succeed ())
2386+
2387+ // Missing condition resolves to Unknown != True, so anyOf is not satisfied
2388+ // Taint should have been added (node has none, conditions not satisfied)
2389+ Expect (anyOfController .hasTaintBySpec (anyOfNode , rule .Spec .Taint )).To (BeTrue ())
2390+ })
2391+ })
22382392})
0 commit comments