@@ -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}
@@ -1452,3 +1552,156 @@ func Test_shouldNodeHaveOutdatedTaint(t *testing.T) {
14521552 })
14531553 }
14541554}
1555+
1556+ func Test_propagateMachineTaintsToNode (t * testing.T ) {
1557+ alwaysTaint := clusterv1.MachineTaint {
1558+ Key : "added-always" ,
1559+ Value : ptr .To ("always-value" ),
1560+ Effect : corev1 .TaintEffectNoSchedule ,
1561+ Propagation : clusterv1 .TaintPropagationAlways ,
1562+ }
1563+ onInitializationTaint := clusterv1.MachineTaint {
1564+ Key : "added-on-initialization" ,
1565+ Value : ptr .To ("on-initialization-value" ),
1566+ Effect : corev1 .TaintEffectNoSchedule ,
1567+ Propagation : clusterv1 .TaintPropagationOnInitialization ,
1568+ }
1569+
1570+ existingAlwaysTaint := clusterv1.MachineTaint {
1571+ Key : "existing-always" ,
1572+ Value : ptr .To ("existing-always-value" ),
1573+ Effect : corev1 .TaintEffectNoExecute ,
1574+ Propagation : clusterv1 .TaintPropagationAlways ,
1575+ }
1576+
1577+ transitionAlways := clusterv1.MachineTaint {
1578+ Key : "transition-taint" ,
1579+ Value : ptr .To ("transition-value" ),
1580+ Effect : corev1 .TaintEffectNoSchedule ,
1581+ Propagation : clusterv1 .TaintPropagationAlways ,
1582+ }
1583+ transitionOnInitialization := transitionAlways
1584+ transitionOnInitialization .Propagation = clusterv1 .TaintPropagationOnInitialization
1585+
1586+ tests := []struct {
1587+ name string
1588+ node * corev1.Node
1589+ machineTaints []clusterv1.MachineTaint
1590+ expectedTaints []corev1.Taint
1591+ expectedAnnotation string
1592+ expectChanged bool
1593+ }{
1594+ {
1595+ name : "no taints set, no taints to set, adds empty annotation" ,
1596+ node : builder .Node ("" ).Build (),
1597+ machineTaints : []clusterv1.MachineTaint {},
1598+ expectedTaints : nil ,
1599+ expectedAnnotation : "" ,
1600+ expectChanged : true ,
1601+ },
1602+ {
1603+ name : "no taints set, no taints to set, keeps empty annotation" ,
1604+ node : builder .Node ("" ).WithAnnotations (map [string ]string {clusterv1 .TaintsFromMachineAnnotation : "" }).Build (),
1605+ machineTaints : []clusterv1.MachineTaint {},
1606+ expectedTaints : nil ,
1607+ expectedAnnotation : "" ,
1608+ expectChanged : false ,
1609+ },
1610+ {
1611+ name : "no taints set, no taints to set, cleans up empty annotation" ,
1612+ node : builder .Node ("" ).WithAnnotations (map [string ]string {clusterv1 .TaintsFromMachineAnnotation : "does-not-exist:NoSchedule" }).Build (),
1613+ machineTaints : []clusterv1.MachineTaint {},
1614+ expectedTaints : []corev1.Taint {}, // The taints utility initializes an empty slice on no-op removal.
1615+ expectedAnnotation : "" ,
1616+ expectChanged : true ,
1617+ },
1618+ // Basic Always taint operations:
1619+ {
1620+ name : "Add missing Always taint, no tracking annotation, no other taints" ,
1621+ node : builder .Node ("" ).Build (),
1622+ machineTaints : []clusterv1.MachineTaint {alwaysTaint },
1623+ expectedTaints : []corev1.Taint {convertMachineTaintToCoreV1Taint (alwaysTaint )},
1624+ expectedAnnotation : "added-always:NoSchedule" ,
1625+ expectChanged : true ,
1626+ },
1627+ {
1628+ name : "Add missing Always taint, tracking annotation, no other taints" ,
1629+ node : builder .Node ("" ).
1630+ WithAnnotations (map [string ]string {clusterv1 .TaintsFromMachineAnnotation : "" }).Build (),
1631+ machineTaints : []clusterv1.MachineTaint {alwaysTaint },
1632+ expectedTaints : []corev1.Taint {convertMachineTaintToCoreV1Taint (alwaysTaint )},
1633+ expectedAnnotation : "added-always:NoSchedule" ,
1634+ expectChanged : true ,
1635+ },
1636+ {
1637+ name : "Delete Always taint, tracking annotation, no other taints" ,
1638+ node : builder .Node ("" ).
1639+ WithAnnotations (map [string ]string {clusterv1 .TaintsFromMachineAnnotation : "existing-always:NoExecute" }).
1640+ WithTaints (convertMachineTaintToCoreV1Taint (existingAlwaysTaint )).Build (),
1641+ machineTaints : []clusterv1.MachineTaint {},
1642+ expectedTaints : []corev1.Taint {},
1643+ expectedAnnotation : "" ,
1644+ expectChanged : true ,
1645+ },
1646+ // Basic OnInitialization taint operations:
1647+ {
1648+ name : "Add missing OnInitialization taint, no tracking annotation, no other taints" ,
1649+ node : builder .Node ("" ).Build (),
1650+ machineTaints : []clusterv1.MachineTaint {onInitializationTaint },
1651+ expectedTaints : []corev1.Taint {convertMachineTaintToCoreV1Taint (onInitializationTaint )},
1652+ expectedAnnotation : "" ,
1653+ expectChanged : true ,
1654+ },
1655+ {
1656+ name : "Don't add missing OnInitialization taint, tracking annotation, no other taints" ,
1657+ node : builder .Node ("" ).
1658+ WithAnnotations (map [string ]string {clusterv1 .TaintsFromMachineAnnotation : "" }).Build (),
1659+ machineTaints : []clusterv1.MachineTaint {onInitializationTaint },
1660+ expectedTaints : nil ,
1661+ expectedAnnotation : "" ,
1662+ expectChanged : false ,
1663+ },
1664+ {
1665+ name : "Don't delete OnInitialization taint, tracking annotation, no other taints" ,
1666+ node : builder .Node ("" ).
1667+ WithAnnotations (map [string ]string {clusterv1 .TaintsFromMachineAnnotation : "" }).
1668+ WithTaints (convertMachineTaintToCoreV1Taint (onInitializationTaint )).Build (),
1669+ machineTaints : []clusterv1.MachineTaint {},
1670+ expectedTaints : []corev1.Taint {convertMachineTaintToCoreV1Taint (onInitializationTaint )},
1671+ expectedAnnotation : "" ,
1672+ expectChanged : false ,
1673+ },
1674+ // Transitions
1675+ {
1676+ name : "Transition Always to OnInitialization should remove from annotation but be kept on the node" ,
1677+ node : builder .Node ("" ).
1678+ WithAnnotations (map [string ]string {clusterv1 .TaintsFromMachineAnnotation : "transition-taint:NoSchedule" }).
1679+ WithTaints (convertMachineTaintToCoreV1Taint (transitionAlways )).Build (),
1680+ machineTaints : []clusterv1.MachineTaint {transitionOnInitialization },
1681+ expectedTaints : []corev1.Taint {convertMachineTaintToCoreV1Taint (transitionAlways )},
1682+ expectedAnnotation : "" ,
1683+ expectChanged : true ,
1684+ },
1685+ {
1686+ name : "Transition OnInitialization to Always should add to annotation and be kept on the node" ,
1687+ node : builder .Node ("" ).
1688+ WithAnnotations (map [string ]string {clusterv1 .TaintsFromMachineAnnotation : "" }).
1689+ WithTaints (convertMachineTaintToCoreV1Taint (transitionOnInitialization )).Build (),
1690+ machineTaints : []clusterv1.MachineTaint {transitionAlways },
1691+ expectedTaints : []corev1.Taint {convertMachineTaintToCoreV1Taint (transitionOnInitialization )},
1692+ expectedAnnotation : "transition-taint:NoSchedule" ,
1693+ expectChanged : true ,
1694+ },
1695+ }
1696+ for _ , tt := range tests {
1697+ t .Run (tt .name , func (t * testing.T ) {
1698+ g := NewWithT (t )
1699+ changed , err := propagateMachineTaintsToNode (tt .node , tt .machineTaints )
1700+ g .Expect (err ).ToNot (HaveOccurred ())
1701+ g .Expect (changed ).To (Equal (tt .expectChanged ))
1702+ g .Expect (tt .node .Spec .Taints ).To (Equal (tt .expectedTaints ))
1703+ g .Expect (tt .node .Annotations ).To (HaveKey (clusterv1 .TaintsFromMachineAnnotation ))
1704+ g .Expect (tt .node .Annotations [clusterv1 .TaintsFromMachineAnnotation ]).To (Equal (tt .expectedAnnotation ))
1705+ })
1706+ }
1707+ }
0 commit comments