@@ -27,6 +27,7 @@ import (
2727 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2828 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2929 "k8s.io/client-go/tools/record"
30+ utilfeature "k8s.io/component-base/featuregate/testing"
3031 "k8s.io/utils/ptr"
3132 ctrl "sigs.k8s.io/controller-runtime"
3233 "sigs.k8s.io/controller-runtime/pkg/client"
@@ -39,6 +40,7 @@ import (
3940 "sigs.k8s.io/cluster-api/api/core/v1beta2/index"
4041 "sigs.k8s.io/cluster-api/controllers/clustercache"
4142 "sigs.k8s.io/cluster-api/controllers/remote"
43+ "sigs.k8s.io/cluster-api/feature"
4244 "sigs.k8s.io/cluster-api/internal/topology/ownerrefs"
4345 "sigs.k8s.io/cluster-api/util"
4446 "sigs.k8s.io/cluster-api/util/kubeconfig"
@@ -59,6 +61,22 @@ func TestReconcileNode(t *testing.T) {
5961 },
6062 }
6163
64+ defaultMachineWithTaints := defaultMachine .DeepCopy ()
65+ defaultMachineWithTaints .Spec .Taints = []clusterv1.MachineTaint {
66+ {
67+ Key : "test-always-taint" ,
68+ Value : ptr .To ("test-value1" ),
69+ Effect : corev1 .TaintEffectNoSchedule ,
70+ Propagation : clusterv1 .TaintPropagationAlways ,
71+ },
72+ {
73+ Key : "test-on-initialization-taint" ,
74+ Value : ptr .To ("test-value2" ),
75+ Effect : corev1 .TaintEffectNoSchedule ,
76+ Propagation : clusterv1 .TaintPropagationOnInitialization ,
77+ },
78+ }
79+
6280 defaultCluster := & clusterv1.Cluster {
6381 ObjectMeta : metav1.ObjectMeta {
6482 Name : "test-cluster" ,
@@ -75,6 +93,7 @@ func TestReconcileNode(t *testing.T) {
7593 expectError bool
7694 expected func (g * WithT , m * clusterv1.Machine )
7795 expectNodeGetError bool
96+ expectedNode func (g * WithT , m * corev1.Node )
7897 }{
7998 {
8099 name : "No op if provider ID is not set" ,
@@ -186,12 +205,87 @@ func TestReconcileNode(t *testing.T) {
186205 expectResult : ctrl.Result {},
187206 expectError : false ,
188207 },
208+ {
209+ name : "node found, should propagate taints" ,
210+ machine : defaultMachineWithTaints .DeepCopy (),
211+ node : & corev1.Node {
212+ ObjectMeta : metav1.ObjectMeta {
213+ Name : "test-node-1" ,
214+ },
215+ Spec : corev1.NodeSpec {
216+ ProviderID : "aws://us-east-1/test-node-1" ,
217+ },
218+ Status : corev1.NodeStatus {
219+ NodeInfo : corev1.NodeSystemInfo {
220+ MachineID : "foo" ,
221+ },
222+ Addresses : []corev1.NodeAddress {
223+ {
224+ Type : corev1 .NodeInternalIP ,
225+ Address : "1.1.1.1" ,
226+ },
227+ },
228+ },
229+ },
230+ nodeGetErr : false ,
231+ expectResult : ctrl.Result {},
232+ expectError : false ,
233+ expectedNode : func (g * WithT , n * corev1.Node ) {
234+ g .Expect (n .Spec .Taints ).To (BeComparableTo ([]corev1.Taint {
235+ {
236+ Key : "test-always-taint" ,
237+ Value : "test-value1" ,
238+ Effect : corev1 .TaintEffectNoSchedule ,
239+ },
240+ {
241+ Key : "test-on-initialization-taint" ,
242+ Value : "test-value2" ,
243+ Effect : corev1 .TaintEffectNoSchedule ,
244+ },
245+ }))
246+ g .Expect (n .Annotations [clusterv1 .TaintsFromMachineAnnotation ]).To (Equal ("test-always-taint:NoSchedule" ))
247+ },
248+ },
249+ {
250+ name : "node found, should not add taints annotation if taints feature gate is disabled" ,
251+ machine : defaultMachine .DeepCopy (), // The test only enables the feature gate if machine has taints.
252+ node : & corev1.Node {
253+ ObjectMeta : metav1.ObjectMeta {
254+ Name : "test-node-1" ,
255+ },
256+ Spec : corev1.NodeSpec {
257+ ProviderID : "aws://us-east-1/test-node-1" ,
258+ },
259+ Status : corev1.NodeStatus {
260+ NodeInfo : corev1.NodeSystemInfo {
261+ MachineID : "foo" ,
262+ },
263+ Addresses : []corev1.NodeAddress {
264+ {
265+ Type : corev1 .NodeInternalIP ,
266+ Address : "1.1.1.1" ,
267+ },
268+ },
269+ },
270+ },
271+ nodeGetErr : false ,
272+ expectResult : ctrl.Result {},
273+ expectError : false ,
274+ expectedNode : func (g * WithT , n * corev1.Node ) {
275+ g .Expect (n .Spec .Taints ).To (BeEmpty ())
276+ g .Expect (n .Annotations ).ToNot (HaveKey (clusterv1 .TaintsFromMachineAnnotation ))
277+ },
278+ },
189279 }
190280
191281 for _ , tc := range testCases {
192282 t .Run (tc .name , func (t * testing.T ) {
193283 g := NewWithT (t )
194284
285+ if tc .machine .Spec .Taints != nil {
286+ utilfeature .SetFeatureGateDuringTest (t , feature .Gates , feature .MachineTaintPropagation , true )
287+ }
288+
195289 c := fake .NewClientBuilder ().WithObjects (tc .machine ).WithIndex (& corev1.Node {}, "spec.providerID" , index .NodeByProviderID ).Build ()
196290 if tc .nodeGetErr {
197291 c = fake .NewClientBuilder ().WithObjects (tc .machine ).Build () // No Index
@@ -221,6 +315,12 @@ func TestReconcileNode(t *testing.T) {
221315 }
222316
223317 g .Expect (s .nodeGetError != nil ).To (Equal (tc .expectNodeGetError ))
318+
319+ if tc .expectedNode != nil {
320+ node := & corev1.Node {}
321+ g .Expect (c .Get (ctx , client .ObjectKeyFromObject (tc .node ), node )).To (Succeed ())
322+ tc .expectedNode (g , node )
323+ }
224324 })
225325 }
226326}
@@ -1489,3 +1589,156 @@ func Test_shouldNodeHaveOutdatedTaint(t *testing.T) {
14891589 })
14901590 }
14911591}
1592+
1593+ func Test_propagateMachineTaintsToNode (t * testing.T ) {
1594+ alwaysTaint := clusterv1.MachineTaint {
1595+ Key : "added-always" ,
1596+ Value : ptr .To ("always-value" ),
1597+ Effect : corev1 .TaintEffectNoSchedule ,
1598+ Propagation : clusterv1 .TaintPropagationAlways ,
1599+ }
1600+ onInitializationTaint := clusterv1.MachineTaint {
1601+ Key : "added-on-initialization" ,
1602+ Value : ptr .To ("on-initialization-value" ),
1603+ Effect : corev1 .TaintEffectNoSchedule ,
1604+ Propagation : clusterv1 .TaintPropagationOnInitialization ,
1605+ }
1606+
1607+ existingAlwaysTaint := clusterv1.MachineTaint {
1608+ Key : "existing-always" ,
1609+ Value : ptr .To ("existing-always-value" ),
1610+ Effect : corev1 .TaintEffectNoExecute ,
1611+ Propagation : clusterv1 .TaintPropagationAlways ,
1612+ }
1613+
1614+ transitionAlways := clusterv1.MachineTaint {
1615+ Key : "transition-taint" ,
1616+ Value : ptr .To ("transition-value" ),
1617+ Effect : corev1 .TaintEffectNoSchedule ,
1618+ Propagation : clusterv1 .TaintPropagationAlways ,
1619+ }
1620+ transitionOnInitialization := transitionAlways
1621+ transitionOnInitialization .Propagation = clusterv1 .TaintPropagationOnInitialization
1622+
1623+ tests := []struct {
1624+ name string
1625+ node * corev1.Node
1626+ machineTaints []clusterv1.MachineTaint
1627+ expectedTaints []corev1.Taint
1628+ expectedAnnotation string
1629+ expectChanged bool
1630+ }{
1631+ {
1632+ name : "no taints set, no taints to set, adds empty annotation" ,
1633+ node : builder .Node ("" ).Build (),
1634+ machineTaints : []clusterv1.MachineTaint {},
1635+ expectedTaints : nil ,
1636+ expectedAnnotation : "" ,
1637+ expectChanged : true ,
1638+ },
1639+ {
1640+ name : "no taints set, no taints to set, keeps empty annotation" ,
1641+ node : builder .Node ("" ).WithAnnotations (map [string ]string {clusterv1 .TaintsFromMachineAnnotation : "" }).Build (),
1642+ machineTaints : []clusterv1.MachineTaint {},
1643+ expectedTaints : nil ,
1644+ expectedAnnotation : "" ,
1645+ expectChanged : false ,
1646+ },
1647+ {
1648+ name : "no taints set, no taints to set, cleans up empty annotation" ,
1649+ node : builder .Node ("" ).WithAnnotations (map [string ]string {clusterv1 .TaintsFromMachineAnnotation : "does-not-exist:NoSchedule" }).Build (),
1650+ machineTaints : []clusterv1.MachineTaint {},
1651+ expectedTaints : []corev1.Taint {}, // The taints utility initializes an empty slice on no-op removal.
1652+ expectedAnnotation : "" ,
1653+ expectChanged : true ,
1654+ },
1655+ // Basic Always taint operations:
1656+ {
1657+ name : "Add missing Always taint, no tracking annotation, no other taints" ,
1658+ node : builder .Node ("" ).Build (),
1659+ machineTaints : []clusterv1.MachineTaint {alwaysTaint },
1660+ expectedTaints : []corev1.Taint {convertMachineTaintToCoreV1Taint (alwaysTaint )},
1661+ expectedAnnotation : "added-always:NoSchedule" ,
1662+ expectChanged : true ,
1663+ },
1664+ {
1665+ name : "Add missing Always taint, tracking annotation, no other taints" ,
1666+ node : builder .Node ("" ).
1667+ WithAnnotations (map [string ]string {clusterv1 .TaintsFromMachineAnnotation : "" }).Build (),
1668+ machineTaints : []clusterv1.MachineTaint {alwaysTaint },
1669+ expectedTaints : []corev1.Taint {convertMachineTaintToCoreV1Taint (alwaysTaint )},
1670+ expectedAnnotation : "added-always:NoSchedule" ,
1671+ expectChanged : true ,
1672+ },
1673+ {
1674+ name : "Delete Always taint, tracking annotation, no other taints" ,
1675+ node : builder .Node ("" ).
1676+ WithAnnotations (map [string ]string {clusterv1 .TaintsFromMachineAnnotation : "existing-always:NoExecute" }).
1677+ WithTaints (convertMachineTaintToCoreV1Taint (existingAlwaysTaint )).Build (),
1678+ machineTaints : []clusterv1.MachineTaint {},
1679+ expectedTaints : []corev1.Taint {},
1680+ expectedAnnotation : "" ,
1681+ expectChanged : true ,
1682+ },
1683+ // Basic OnInitialization taint operations:
1684+ {
1685+ name : "Add missing OnInitialization taint, no tracking annotation, no other taints" ,
1686+ node : builder .Node ("" ).Build (),
1687+ machineTaints : []clusterv1.MachineTaint {onInitializationTaint },
1688+ expectedTaints : []corev1.Taint {convertMachineTaintToCoreV1Taint (onInitializationTaint )},
1689+ expectedAnnotation : "" ,
1690+ expectChanged : true ,
1691+ },
1692+ {
1693+ name : "Don't add missing OnInitialization taint, tracking annotation, no other taints" ,
1694+ node : builder .Node ("" ).
1695+ WithAnnotations (map [string ]string {clusterv1 .TaintsFromMachineAnnotation : "" }).Build (),
1696+ machineTaints : []clusterv1.MachineTaint {onInitializationTaint },
1697+ expectedTaints : nil ,
1698+ expectedAnnotation : "" ,
1699+ expectChanged : false ,
1700+ },
1701+ {
1702+ name : "Don't delete OnInitialization taint, tracking annotation, no other taints" ,
1703+ node : builder .Node ("" ).
1704+ WithAnnotations (map [string ]string {clusterv1 .TaintsFromMachineAnnotation : "" }).
1705+ WithTaints (convertMachineTaintToCoreV1Taint (onInitializationTaint )).Build (),
1706+ machineTaints : []clusterv1.MachineTaint {},
1707+ expectedTaints : []corev1.Taint {convertMachineTaintToCoreV1Taint (onInitializationTaint )},
1708+ expectedAnnotation : "" ,
1709+ expectChanged : false ,
1710+ },
1711+ // Transitions
1712+ {
1713+ name : "Transition Always to OnInitialization should remove from annotation but be kept on the node" ,
1714+ node : builder .Node ("" ).
1715+ WithAnnotations (map [string ]string {clusterv1 .TaintsFromMachineAnnotation : "transition-taint:NoSchedule" }).
1716+ WithTaints (convertMachineTaintToCoreV1Taint (transitionAlways )).Build (),
1717+ machineTaints : []clusterv1.MachineTaint {transitionOnInitialization },
1718+ expectedTaints : []corev1.Taint {convertMachineTaintToCoreV1Taint (transitionAlways )},
1719+ expectedAnnotation : "" ,
1720+ expectChanged : true ,
1721+ },
1722+ {
1723+ name : "Transition OnInitialization to Always should add to annotation and be kept on the node" ,
1724+ node : builder .Node ("" ).
1725+ WithAnnotations (map [string ]string {clusterv1 .TaintsFromMachineAnnotation : "" }).
1726+ WithTaints (convertMachineTaintToCoreV1Taint (transitionOnInitialization )).Build (),
1727+ machineTaints : []clusterv1.MachineTaint {transitionAlways },
1728+ expectedTaints : []corev1.Taint {convertMachineTaintToCoreV1Taint (transitionOnInitialization )},
1729+ expectedAnnotation : "transition-taint:NoSchedule" ,
1730+ expectChanged : true ,
1731+ },
1732+ }
1733+ for _ , tt := range tests {
1734+ t .Run (tt .name , func (t * testing.T ) {
1735+ g := NewWithT (t )
1736+ changed , err := propagateMachineTaintsToNode (tt .node , tt .machineTaints )
1737+ g .Expect (err ).ToNot (HaveOccurred ())
1738+ g .Expect (changed ).To (Equal (tt .expectChanged ))
1739+ g .Expect (tt .node .Spec .Taints ).To (Equal (tt .expectedTaints ))
1740+ g .Expect (tt .node .Annotations ).To (HaveKey (clusterv1 .TaintsFromMachineAnnotation ))
1741+ g .Expect (tt .node .Annotations [clusterv1 .TaintsFromMachineAnnotation ]).To (Equal (tt .expectedAnnotation ))
1742+ })
1743+ }
1744+ }
0 commit comments