@@ -445,7 +445,6 @@ var _ = Describe("NodeReadinessRule Controller", func() {
445445 Key : "readiness.k8s.io/missing-key" ,
446446 Effect : corev1 .TaintEffectNoSchedule ,
447447 }
448-
449448 hasTaint = readinessController .hasTaintBySpec (node , nonExistentTaint )
450449 Expect (hasTaint ).To (BeFalse ())
451450 })
@@ -958,7 +957,7 @@ var _ = Describe("NodeReadinessRule Controller", func() {
958957 })
959958 })
960959
961- Context ("when a rule's nodeSelector changes " , func () {
960+ Context ("when a rule's nodeSelector is modified " , func () {
962961 var rule * nodereadinessiov1alpha1.NodeReadinessRule
963962 var prodNode , devNode * corev1.Node
964963
@@ -1016,78 +1015,93 @@ var _ = Describe("NodeReadinessRule Controller", func() {
10161015 _ = k8sClient .Delete (ctx , rule )
10171016 })
10181017
1019- It ("should remove taints from nodes that no longer match the selector" , func () {
1020- // Initial reconcile - prod node should be managed, dev node should not
1021- _ , err := ruleReconciler .Reconcile (ctx , reconcile.Request {NamespacedName : types.NamespacedName {Name : "selector-change-rule" }})
1022- Expect (err ).NotTo (HaveOccurred ())
1023-
1024- // Verify prod node still has taint (condition not met)
1025- Eventually (func () bool {
1026- updatedNode := & corev1.Node {}
1027- if err := k8sClient .Get (ctx , types.NamespacedName {Name : "prod-node" }, updatedNode ); err != nil {
1028- return false
1029- }
1030- for _ , taint := range updatedNode .Spec .Taints {
1031- if taint .Key == selectorChangeTaintKey {
1032- return true
1033- }
1034- }
1035- return false
1036- }, time .Second * 5 ).Should (BeTrue (), "Prod node should have taint" )
1037-
1038- // Verify dev node does not have taint (not selected)
1039- Consistently (func () bool {
1040- updatedNode := & corev1.Node {}
1041- if err := k8sClient .Get (ctx , types.NamespacedName {Name : "dev-node" }, updatedNode ); err != nil {
1042- return false
1043- }
1044- for _ , taint := range updatedNode .Spec .Taints {
1045- if taint .Key == selectorChangeTaintKey {
1046- return false // Taint found (unexpected)
1047- }
1048- }
1049- return true // No taint (expected)
1050- }, time .Second * 2 ).Should (BeTrue (), "Dev node should not have taint" )
1051-
1052- // Update rule to target dev nodes instead of prod nodes
1018+ It ("should reject attempts to change the nodeSelector (immutable)" , func () {
10531019 updatedRule := & nodereadinessiov1alpha1.NodeReadinessRule {}
10541020 Expect (k8sClient .Get (ctx , types.NamespacedName {Name : "selector-change-rule" }, updatedRule )).To (Succeed ())
10551021 updatedRule .Spec .NodeSelector = metav1.LabelSelector {
10561022 MatchLabels : map [string ]string {"env" : "dev" },
10571023 }
1058- Expect (k8sClient .Update (ctx , updatedRule )).To (Succeed ())
1024+ err := k8sClient .Update (ctx , updatedRule )
1025+ Expect (err ).To (HaveOccurred ())
1026+ Expect (err .Error ()).To (ContainSubstring ("nodeSelector is immutable" ))
1027+ })
1028+ })
10591029
1060- // Trigger reconciliation
1061- _ , err = ruleReconciler .Reconcile (ctx , reconcile.Request {NamespacedName : types.NamespacedName {Name : "selector-change-rule" }})
1062- Expect (err ).NotTo (HaveOccurred ())
1030+ Context ("when attempting to modify immutable fields" , func () {
1031+ var rule * nodereadinessiov1alpha1.NodeReadinessRule
10631032
1064- // Verify taint is removed from prod node (no longer selected)
1065- Eventually (func () bool {
1066- updatedNode := & corev1.Node {}
1067- if err := k8sClient .Get (ctx , types.NamespacedName {Name : "prod-node" }, updatedNode ); err != nil {
1068- return false
1069- }
1070- for _ , taint := range updatedNode .Spec .Taints {
1071- if taint .Key == selectorChangeTaintKey {
1072- return false // Taint still exists
1073- }
1074- }
1075- return true // Taint removed
1076- }, time .Second * 10 ).Should (BeTrue (), "Prod node taint should be removed after selector change" )
1033+ BeforeEach (func () {
1034+ rule = & nodereadinessiov1alpha1.NodeReadinessRule {
1035+ ObjectMeta : metav1.ObjectMeta {
1036+ Name : "immutability-test-rule" ,
1037+ },
1038+ Spec : nodereadinessiov1alpha1.NodeReadinessRuleSpec {
1039+ Conditions : []nodereadinessiov1alpha1.ConditionRequirement {
1040+ {Type : "Ready" , RequiredStatus : corev1 .ConditionTrue },
1041+ },
1042+ Taint : corev1.Taint {
1043+ Key : "readiness.k8s.io/immutable" ,
1044+ Effect : corev1 .TaintEffectNoSchedule ,
1045+ Value : "test-value" ,
1046+ },
1047+ EnforcementMode : nodereadinessiov1alpha1 .EnforcementModeBootstrapOnly ,
1048+ NodeSelector : metav1.LabelSelector {
1049+ MatchLabels : map [string ]string {"test" : "immutable" },
1050+ },
1051+ },
1052+ }
1053+ Expect (k8sClient .Create (ctx , rule )).To (Succeed ())
1054+ })
10771055
1078- // Verify dev node now gets taint (newly selected, condition not met)
1079- Eventually (func () bool {
1080- updatedNode := & corev1.Node {}
1081- if err := k8sClient .Get (ctx , types.NamespacedName {Name : "dev-node" }, updatedNode ); err != nil {
1082- return false
1083- }
1084- for _ , taint := range updatedNode .Spec .Taints {
1085- if taint .Key == selectorChangeTaintKey {
1086- return true
1087- }
1088- }
1089- return false
1090- }, time .Second * 10 ).Should (BeTrue (), "Dev node should now have taint after selector change" )
1056+ AfterEach (func () {
1057+ _ = k8sClient .Delete (ctx , rule )
1058+ })
1059+
1060+ It ("should reject attempts to change taint.key" , func () {
1061+ updatedRule := & nodereadinessiov1alpha1.NodeReadinessRule {}
1062+ Expect (k8sClient .Get (ctx , types.NamespacedName {Name : "immutability-test-rule" }, updatedRule )).To (Succeed ())
1063+ updatedRule .Spec .Taint .Key = "readiness.k8s.io/different-key"
1064+ err := k8sClient .Update (ctx , updatedRule )
1065+ Expect (err ).To (HaveOccurred ())
1066+ Expect (err .Error ()).To (ContainSubstring ("taint key is immutable" ))
1067+ })
1068+
1069+ It ("should reject attempts to change taint.effect" , func () {
1070+ updatedRule := & nodereadinessiov1alpha1.NodeReadinessRule {}
1071+ Expect (k8sClient .Get (ctx , types.NamespacedName {Name : "immutability-test-rule" }, updatedRule )).To (Succeed ())
1072+ updatedRule .Spec .Taint .Effect = corev1 .TaintEffectNoExecute
1073+ err := k8sClient .Update (ctx , updatedRule )
1074+ Expect (err ).To (HaveOccurred ())
1075+ Expect (err .Error ()).To (ContainSubstring ("taint effect is immutable" ))
1076+ })
1077+
1078+ It ("should reject attempts to change taint.value" , func () {
1079+ updatedRule := & nodereadinessiov1alpha1.NodeReadinessRule {}
1080+ Expect (k8sClient .Get (ctx , types.NamespacedName {Name : "immutability-test-rule" }, updatedRule )).To (Succeed ())
1081+ updatedRule .Spec .Taint .Value = "different-value"
1082+ err := k8sClient .Update (ctx , updatedRule )
1083+ Expect (err ).To (HaveOccurred ())
1084+ Expect (err .Error ()).To (ContainSubstring ("taint value is immutable" ))
1085+ })
1086+
1087+ It ("should reject attempts to change conditions" , func () {
1088+ updatedRule := & nodereadinessiov1alpha1.NodeReadinessRule {}
1089+ Expect (k8sClient .Get (ctx , types.NamespacedName {Name : "immutability-test-rule" }, updatedRule )).To (Succeed ())
1090+ updatedRule .Spec .Conditions = []nodereadinessiov1alpha1.ConditionRequirement {
1091+ {Type : "DiskPressure" , RequiredStatus : corev1 .ConditionFalse },
1092+ }
1093+ err := k8sClient .Update (ctx , updatedRule )
1094+ Expect (err ).To (HaveOccurred ())
1095+ Expect (err .Error ()).To (ContainSubstring ("conditions is immutable" ))
1096+ })
1097+
1098+ It ("should reject attempts to change enforcementMode" , func () {
1099+ updatedRule := & nodereadinessiov1alpha1.NodeReadinessRule {}
1100+ Expect (k8sClient .Get (ctx , types.NamespacedName {Name : "immutability-test-rule" }, updatedRule )).To (Succeed ())
1101+ updatedRule .Spec .EnforcementMode = nodereadinessiov1alpha1 .EnforcementModeContinuous
1102+ err := k8sClient .Update (ctx , updatedRule )
1103+ Expect (err ).To (HaveOccurred ())
1104+ Expect (err .Error ()).To (ContainSubstring ("enforcementMode is immutable" ))
10911105 })
10921106 })
10931107
0 commit comments