@@ -26,13 +26,15 @@ import (
2626 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2727 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2828 "k8s.io/client-go/tools/record"
29+ utilfeature "k8s.io/component-base/featuregate/testing"
2930 "k8s.io/utils/pointer"
3031 "sigs.k8s.io/controller-runtime/pkg/client"
3132 "sigs.k8s.io/controller-runtime/pkg/client/fake"
3233 "sigs.k8s.io/controller-runtime/pkg/reconcile"
3334
3435 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
3536 "sigs.k8s.io/cluster-api/controllers/external"
37+ "sigs.k8s.io/cluster-api/feature"
3638 "sigs.k8s.io/cluster-api/internal/contract"
3739 "sigs.k8s.io/cluster-api/internal/test/builder"
3840 "sigs.k8s.io/cluster-api/internal/util/ssa"
@@ -1326,6 +1328,190 @@ func TestMachineSetReconciler_syncMachines(t *testing.T) {
13261328 }, 5 * time .Second ).Should (Succeed ())
13271329}
13281330
1331+ func TestMachineSetReconciler_reconcileUnhealthyMachines (t * testing.T ) {
1332+ t .Run ("should delete unhealthy machines if preflight checks pass" , func (t * testing.T ) {
1333+ defer utilfeature .SetFeatureGateDuringTest (t , feature .Gates , feature .MachineSetPreflightChecks , true )()
1334+
1335+ g := NewWithT (t )
1336+
1337+ controlPlaneStable := builder .ControlPlane ("default" , "cp1" ).
1338+ WithVersion ("v1.26.2" ).
1339+ WithStatusFields (map [string ]interface {}{
1340+ "status.version" : "v1.26.2" ,
1341+ }).
1342+ Build ()
1343+ cluster := & clusterv1.Cluster {
1344+ ObjectMeta : metav1.ObjectMeta {
1345+ Name : "test-cluster" ,
1346+ Namespace : "default" ,
1347+ },
1348+ Spec : clusterv1.ClusterSpec {
1349+ ControlPlaneRef : contract .ObjToRef (controlPlaneStable ),
1350+ },
1351+ }
1352+ machineSet := & clusterv1.MachineSet {}
1353+
1354+ unhealthyMachine := & clusterv1.Machine {
1355+ ObjectMeta : metav1.ObjectMeta {
1356+ Name : "unhealthy-machine" ,
1357+ Namespace : "default" ,
1358+ },
1359+ Status : clusterv1.MachineStatus {
1360+ Conditions : []clusterv1.Condition {
1361+ {
1362+ Type : clusterv1 .MachineOwnerRemediatedCondition ,
1363+ Status : corev1 .ConditionFalse ,
1364+ },
1365+ },
1366+ },
1367+ }
1368+ healthyMachine := & clusterv1.Machine {
1369+ ObjectMeta : metav1.ObjectMeta {
1370+ Name : "healthy-machine" ,
1371+ Namespace : "default" ,
1372+ },
1373+ }
1374+
1375+ machines := []* clusterv1.Machine {unhealthyMachine , healthyMachine }
1376+
1377+ fakeClient := fake .NewClientBuilder ().WithObjects (controlPlaneStable , unhealthyMachine , healthyMachine ).Build ()
1378+ r := & Reconciler {Client : fakeClient }
1379+ _ , err := r .reconcileUnhealthyMachines (ctx , cluster , machineSet , machines )
1380+ g .Expect (err ).To (BeNil ())
1381+ // Verify the unhealthy machine is deleted.
1382+ m := & clusterv1.Machine {}
1383+ err = r .Client .Get (ctx , client .ObjectKeyFromObject (unhealthyMachine ), m )
1384+ g .Expect (apierrors .IsNotFound (err )).To (BeTrue ())
1385+ // Verify the healthy machine is not deleted.
1386+ m = & clusterv1.Machine {}
1387+ g .Expect (r .Client .Get (ctx , client .ObjectKeyFromObject (healthyMachine ), m )).Should (Succeed ())
1388+ })
1389+
1390+ t .Run ("should update the unhealthy machine MachineOwnerRemediated condition if preflight checks did not pass" , func (t * testing.T ) {
1391+ defer utilfeature .SetFeatureGateDuringTest (t , feature .Gates , feature .MachineSetPreflightChecks , true )()
1392+
1393+ g := NewWithT (t )
1394+
1395+ // An upgrading control plane should cause the preflight checks to not pass.
1396+ controlPlaneUpgrading := builder .ControlPlane ("default" , "cp1" ).
1397+ WithVersion ("v1.26.2" ).
1398+ WithStatusFields (map [string ]interface {}{
1399+ "status.version" : "v1.25.2" ,
1400+ }).
1401+ Build ()
1402+ cluster := & clusterv1.Cluster {
1403+ ObjectMeta : metav1.ObjectMeta {
1404+ Name : "test-cluster" ,
1405+ Namespace : "default" ,
1406+ },
1407+ Spec : clusterv1.ClusterSpec {
1408+ ControlPlaneRef : contract .ObjToRef (controlPlaneUpgrading ),
1409+ },
1410+ }
1411+ machineSet := & clusterv1.MachineSet {}
1412+
1413+ unhealthyMachine := & clusterv1.Machine {
1414+ ObjectMeta : metav1.ObjectMeta {
1415+ Name : "unhealthy-machine" ,
1416+ Namespace : "default" ,
1417+ },
1418+ Status : clusterv1.MachineStatus {
1419+ Conditions : []clusterv1.Condition {
1420+ {
1421+ Type : clusterv1 .MachineOwnerRemediatedCondition ,
1422+ Status : corev1 .ConditionFalse ,
1423+ },
1424+ },
1425+ },
1426+ }
1427+ healthyMachine := & clusterv1.Machine {
1428+ ObjectMeta : metav1.ObjectMeta {
1429+ Name : "healthy-machine" ,
1430+ Namespace : "default" ,
1431+ },
1432+ }
1433+
1434+ machines := []* clusterv1.Machine {unhealthyMachine , healthyMachine }
1435+ fakeClient := fake .NewClientBuilder ().WithObjects (controlPlaneUpgrading , unhealthyMachine , healthyMachine ).WithStatusSubresource (& clusterv1.Machine {}).Build ()
1436+ r := & Reconciler {Client : fakeClient }
1437+ _ , err := r .reconcileUnhealthyMachines (ctx , cluster , machineSet , machines )
1438+ g .Expect (err ).To (BeNil ())
1439+
1440+ // Verify the unhealthy machine has the updated condition.
1441+ condition := clusterv1 .MachineOwnerRemediatedCondition
1442+ m := & clusterv1.Machine {}
1443+ g .Expect (r .Client .Get (ctx , client .ObjectKeyFromObject (unhealthyMachine ), m )).To (Succeed ())
1444+ g .Expect (conditions .Has (m , condition )).
1445+ To (BeTrue (), "Machine should have the %s condition set" , condition )
1446+ machineOwnerRemediatedCondition := conditions .Get (m , condition )
1447+ g .Expect (machineOwnerRemediatedCondition .Status ).
1448+ To (Equal (corev1 .ConditionFalse ), "%s condition status should be false" , condition )
1449+ g .Expect (machineOwnerRemediatedCondition .Reason ).
1450+ To (Equal (clusterv1 .WaitingForRemediationReason ), "%s condition should have reason %s" , condition , clusterv1 .WaitingForRemediationReason )
1451+
1452+ // Verify the healthy machine continues to not have the MachineOwnerRemediated condition.
1453+ m = & clusterv1.Machine {}
1454+ g .Expect (r .Client .Get (ctx , client .ObjectKeyFromObject (healthyMachine ), m )).To (Succeed ())
1455+ g .Expect (conditions .Has (m , condition )).
1456+ To (BeFalse (), "Machine should not have the %s condition set" , condition )
1457+ })
1458+ }
1459+
1460+ func TestMachineSetReconciler_syncReplicas (t * testing.T ) {
1461+ t .Run ("should hold off on creating new machines when preflight checks do not pass" , func (t * testing.T ) {
1462+ defer utilfeature .SetFeatureGateDuringTest (t , feature .Gates , feature .MachineSetPreflightChecks , true )()
1463+
1464+ g := NewWithT (t )
1465+
1466+ // An upgrading control plane should cause the preflight checks to not pass.
1467+ controlPlaneUpgrading := builder .ControlPlane ("default" , "test-cp" ).
1468+ WithVersion ("v1.26.2" ).
1469+ WithStatusFields (map [string ]interface {}{
1470+ "status.version" : "v1.25.2" ,
1471+ }).
1472+ Build ()
1473+ cluster := & clusterv1.Cluster {
1474+ ObjectMeta : metav1.ObjectMeta {
1475+ Name : "test-cluster" ,
1476+ Namespace : "default" ,
1477+ },
1478+ Spec : clusterv1.ClusterSpec {
1479+ ControlPlaneRef : contract .ObjToRef (controlPlaneUpgrading ),
1480+ },
1481+ }
1482+ machineSet := & clusterv1.MachineSet {
1483+ ObjectMeta : metav1.ObjectMeta {
1484+ Name : "test-machineset" ,
1485+ Namespace : "default" ,
1486+ },
1487+ Spec : clusterv1.MachineSetSpec {
1488+ Replicas : pointer .Int32 (1 ),
1489+ },
1490+ }
1491+
1492+ fakeClient := fake .NewClientBuilder ().WithObjects (controlPlaneUpgrading , machineSet ).WithStatusSubresource (& clusterv1.MachineSet {}).Build ()
1493+ r := & Reconciler {Client : fakeClient }
1494+ result , err := r .syncReplicas (ctx , cluster , machineSet , nil )
1495+ g .Expect (err ).To (BeNil ())
1496+ g .Expect (result .IsZero ()).To (BeFalse (), "syncReplicas should not return a 'zero' result" )
1497+
1498+ // Verify the proper condition is set on the MachineSet.
1499+ condition := clusterv1 .MachinesCreatedCondition
1500+ g .Expect (conditions .Has (machineSet , condition )).
1501+ To (BeTrue (), "MachineSet should have the %s condition set" , condition )
1502+ machinesCreatedCondition := conditions .Get (machineSet , condition )
1503+ g .Expect (machinesCreatedCondition .Status ).
1504+ To (Equal (corev1 .ConditionFalse ), "%s condition status should be %s" , condition , corev1 .ConditionFalse )
1505+ g .Expect (machinesCreatedCondition .Reason ).
1506+ To (Equal (clusterv1 .PreflightCheckFailedReason ), "%s condition reason should be %s" , condition , clusterv1 .PreflightCheckFailedReason )
1507+
1508+ // Verify no new Machines are created.
1509+ machineList := & clusterv1.MachineList {}
1510+ g .Expect (r .Client .List (ctx , machineList )).To (Succeed ())
1511+ g .Expect (machineList .Items ).To (BeEmpty (), "There should not be any machines" )
1512+ })
1513+ }
1514+
13291515func TestComputeDesiredMachine (t * testing.T ) {
13301516 duration5s := & metav1.Duration {Duration : 5 * time .Second }
13311517 duration10s := & metav1.Duration {Duration : 10 * time .Second }
0 commit comments