sigs.k8s.io/cluster-api@v1.7.1/util/collections/machine_filters_test.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package collections_test
    18  
    19  import (
    20  	"testing"
    21  	"time"
    22  
    23  	. "github.com/onsi/gomega"
    24  	corev1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/utils/ptr"
    27  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    28  
    29  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    30  	controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1"
    31  	"sigs.k8s.io/cluster-api/util/collections"
    32  	"sigs.k8s.io/cluster-api/util/conditions"
    33  )
    34  
    35  func falseFilter(_ *clusterv1.Machine) bool {
    36  	return false
    37  }
    38  
    39  func trueFilter(_ *clusterv1.Machine) bool {
    40  	return true
    41  }
    42  
    43  func TestNot(t *testing.T) {
    44  	t.Run("returns false given a machine filter that returns true", func(t *testing.T) {
    45  		g := NewWithT(t)
    46  		m := &clusterv1.Machine{}
    47  		g.Expect(collections.Not(trueFilter)(m)).To(BeFalse())
    48  	})
    49  	t.Run("returns true given a machine filter that returns false", func(t *testing.T) {
    50  		g := NewWithT(t)
    51  		m := &clusterv1.Machine{}
    52  		g.Expect(collections.Not(falseFilter)(m)).To(BeTrue())
    53  	})
    54  }
    55  
    56  func TestAnd(t *testing.T) {
    57  	t.Run("returns true if both given machine filters return true", func(t *testing.T) {
    58  		g := NewWithT(t)
    59  		m := &clusterv1.Machine{}
    60  		g.Expect(collections.And(trueFilter, trueFilter)(m)).To(BeTrue())
    61  	})
    62  	t.Run("returns false if either given machine filter returns false", func(t *testing.T) {
    63  		g := NewWithT(t)
    64  		m := &clusterv1.Machine{}
    65  		g.Expect(collections.And(trueFilter, falseFilter)(m)).To(BeFalse())
    66  	})
    67  }
    68  
    69  func TestOr(t *testing.T) {
    70  	t.Run("returns true if either given machine filters return true", func(t *testing.T) {
    71  		g := NewWithT(t)
    72  		m := &clusterv1.Machine{}
    73  		g.Expect(collections.Or(trueFilter, falseFilter)(m)).To(BeTrue())
    74  	})
    75  	t.Run("returns false if both given machine filter returns false", func(t *testing.T) {
    76  		g := NewWithT(t)
    77  		m := &clusterv1.Machine{}
    78  		g.Expect(collections.Or(falseFilter, falseFilter)(m)).To(BeFalse())
    79  	})
    80  }
    81  
    82  func TestHasUnhealthyCondition(t *testing.T) {
    83  	t.Run("healthy machine (without HealthCheckSucceeded condition) should return false", func(t *testing.T) {
    84  		g := NewWithT(t)
    85  		m := &clusterv1.Machine{}
    86  		g.Expect(collections.HasUnhealthyCondition(m)).To(BeFalse())
    87  	})
    88  	t.Run("healthy machine (with HealthCheckSucceeded condition == True) should return false", func(t *testing.T) {
    89  		g := NewWithT(t)
    90  		m := &clusterv1.Machine{}
    91  		conditions.MarkTrue(m, clusterv1.MachineHealthCheckSucceededCondition)
    92  		g.Expect(collections.HasUnhealthyCondition(m)).To(BeFalse())
    93  	})
    94  	t.Run("unhealthy machine NOT eligible for KCP remediation (with withHealthCheckSucceeded condition == False but without OwnerRemediated) should return false", func(t *testing.T) {
    95  		g := NewWithT(t)
    96  		m := &clusterv1.Machine{}
    97  		conditions.MarkFalse(m, clusterv1.MachineHealthCheckSucceededCondition, clusterv1.MachineHasFailureReason, clusterv1.ConditionSeverityWarning, "")
    98  		g.Expect(collections.HasUnhealthyCondition(m)).To(BeFalse())
    99  	})
   100  	t.Run("unhealthy machine eligible for KCP (with HealthCheckSucceeded condition == False and with OwnerRemediated) should return true", func(t *testing.T) {
   101  		g := NewWithT(t)
   102  		m := &clusterv1.Machine{}
   103  		conditions.MarkFalse(m, clusterv1.MachineHealthCheckSucceededCondition, clusterv1.MachineHasFailureReason, clusterv1.ConditionSeverityWarning, "")
   104  		conditions.MarkFalse(m, clusterv1.MachineOwnerRemediatedCondition, clusterv1.WaitingForRemediationReason, clusterv1.ConditionSeverityWarning, "")
   105  		g.Expect(collections.HasUnhealthyCondition(m)).To(BeTrue())
   106  	})
   107  }
   108  
   109  func TestHasDeletionTimestamp(t *testing.T) {
   110  	t.Run("machine with deletion timestamp returns true", func(t *testing.T) {
   111  		g := NewWithT(t)
   112  		m := &clusterv1.Machine{}
   113  		now := metav1.Now()
   114  		m.SetDeletionTimestamp(&now)
   115  		g.Expect(collections.HasDeletionTimestamp(m)).To(BeTrue())
   116  	})
   117  	t.Run("machine with nil deletion timestamp returns false", func(t *testing.T) {
   118  		g := NewWithT(t)
   119  		m := &clusterv1.Machine{}
   120  		g.Expect(collections.HasDeletionTimestamp(m)).To(BeFalse())
   121  	})
   122  	t.Run("machine with zero deletion timestamp returns false", func(t *testing.T) {
   123  		g := NewWithT(t)
   124  		m := &clusterv1.Machine{}
   125  		zero := metav1.NewTime(time.Time{})
   126  		m.SetDeletionTimestamp(&zero)
   127  		g.Expect(collections.HasDeletionTimestamp(m)).To(BeFalse())
   128  	})
   129  }
   130  
   131  func TestShouldRolloutAfter(t *testing.T) {
   132  	reconciliationTime := metav1.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
   133  	t.Run("if the machine is nil it returns false", func(t *testing.T) {
   134  		g := NewWithT(t)
   135  		g.Expect(collections.ShouldRolloutAfter(&reconciliationTime, &reconciliationTime)(nil)).To(BeFalse())
   136  	})
   137  	t.Run("if the reconciliationTime is nil it returns false", func(t *testing.T) {
   138  		g := NewWithT(t)
   139  		m := &clusterv1.Machine{}
   140  		g.Expect(collections.ShouldRolloutAfter(nil, &reconciliationTime)(m)).To(BeFalse())
   141  	})
   142  	t.Run("if the rolloutAfter is nil it returns false", func(t *testing.T) {
   143  		g := NewWithT(t)
   144  		m := &clusterv1.Machine{}
   145  		g.Expect(collections.ShouldRolloutAfter(&reconciliationTime, nil)(m)).To(BeFalse())
   146  	})
   147  	t.Run("if rolloutAfter is after the reconciliation time, return false", func(t *testing.T) {
   148  		g := NewWithT(t)
   149  		m := &clusterv1.Machine{}
   150  		rolloutAfter := metav1.NewTime(reconciliationTime.Add(+1 * time.Hour))
   151  		g.Expect(collections.ShouldRolloutAfter(&reconciliationTime, &rolloutAfter)(m)).To(BeFalse())
   152  	})
   153  	t.Run("if rolloutAfter is before the reconciliation time and the machine was created before rolloutAfter, return true", func(t *testing.T) {
   154  		g := NewWithT(t)
   155  		m := &clusterv1.Machine{}
   156  		m.SetCreationTimestamp(metav1.NewTime(reconciliationTime.Add(-2 * time.Hour)))
   157  		rolloutAfter := metav1.NewTime(reconciliationTime.Add(-1 * time.Hour))
   158  		g.Expect(collections.ShouldRolloutAfter(&reconciliationTime, &rolloutAfter)(m)).To(BeTrue())
   159  	})
   160  	t.Run("if rolloutAfter is before the reconciliation time and the machine was created after rolloutAfter, return false", func(t *testing.T) {
   161  		g := NewWithT(t)
   162  		m := &clusterv1.Machine{}
   163  		m.SetCreationTimestamp(metav1.NewTime(reconciliationTime.Add(+1 * time.Hour)))
   164  		rolloutAfter := metav1.NewTime(reconciliationTime.Add(-1 * time.Hour))
   165  		g.Expect(collections.ShouldRolloutAfter(&reconciliationTime, &rolloutAfter)(m)).To(BeFalse())
   166  	})
   167  }
   168  
   169  func TestShouldRolloutBeforeCertificatesExpire(t *testing.T) {
   170  	reconciliationTime := &metav1.Time{Time: time.Now()}
   171  	t.Run("if rolloutBefore is nil it should return false", func(t *testing.T) {
   172  		g := NewWithT(t)
   173  		m := &clusterv1.Machine{}
   174  		g.Expect(collections.ShouldRolloutBefore(reconciliationTime, nil)(m)).To(BeFalse())
   175  	})
   176  	t.Run("if rolloutBefore.certificatesExpiryDays is nil it should return false", func(t *testing.T) {
   177  		g := NewWithT(t)
   178  		m := &clusterv1.Machine{}
   179  		g.Expect(collections.ShouldRolloutBefore(reconciliationTime, &controlplanev1.RolloutBefore{})(m)).To(BeFalse())
   180  	})
   181  	t.Run("if machine is nil it should return false", func(t *testing.T) {
   182  		g := NewWithT(t)
   183  		rb := &controlplanev1.RolloutBefore{CertificatesExpiryDays: ptr.To[int32](10)}
   184  		g.Expect(collections.ShouldRolloutBefore(reconciliationTime, rb)(nil)).To(BeFalse())
   185  	})
   186  	t.Run("if the machine certificate expiry information is not available it should return false", func(t *testing.T) {
   187  		g := NewWithT(t)
   188  		m := &clusterv1.Machine{}
   189  		rb := &controlplanev1.RolloutBefore{CertificatesExpiryDays: ptr.To[int32](10)}
   190  		g.Expect(collections.ShouldRolloutBefore(reconciliationTime, rb)(m)).To(BeFalse())
   191  	})
   192  	t.Run("if the machine certificates are not going to expire within the expiry time it should return false", func(t *testing.T) {
   193  		g := NewWithT(t)
   194  		certificateExpiryTime := reconciliationTime.Add(60 * 24 * time.Hour) // certificates will expire in 60 days from 'now'.
   195  		m := &clusterv1.Machine{
   196  			Status: clusterv1.MachineStatus{
   197  				CertificatesExpiryDate: &metav1.Time{Time: certificateExpiryTime},
   198  			},
   199  		}
   200  		rb := &controlplanev1.RolloutBefore{CertificatesExpiryDays: ptr.To[int32](10)}
   201  		g.Expect(collections.ShouldRolloutBefore(reconciliationTime, rb)(m)).To(BeFalse())
   202  	})
   203  	t.Run("if machine certificates will expire within the expiry time then it should return true", func(t *testing.T) {
   204  		g := NewWithT(t)
   205  		certificateExpiryTime := reconciliationTime.Add(5 * 24 * time.Hour) // certificates will expire in 5 days from 'now'.
   206  		m := &clusterv1.Machine{
   207  			Status: clusterv1.MachineStatus{
   208  				CertificatesExpiryDate: &metav1.Time{Time: certificateExpiryTime},
   209  			},
   210  		}
   211  		rb := &controlplanev1.RolloutBefore{CertificatesExpiryDays: ptr.To[int32](10)}
   212  		g.Expect(collections.ShouldRolloutBefore(reconciliationTime, rb)(m)).To(BeTrue())
   213  	})
   214  }
   215  
   216  func TestHashAnnotationKey(t *testing.T) {
   217  	t.Run("machine with specified annotation returns true", func(t *testing.T) {
   218  		g := NewWithT(t)
   219  		m := &clusterv1.Machine{}
   220  		m.SetAnnotations(map[string]string{"test": ""})
   221  		g.Expect(collections.HasAnnotationKey("test")(m)).To(BeTrue())
   222  	})
   223  	t.Run("machine with specified annotation with non-empty value returns true", func(t *testing.T) {
   224  		g := NewWithT(t)
   225  		m := &clusterv1.Machine{}
   226  		m.SetAnnotations(map[string]string{"test": "blue"})
   227  		g.Expect(collections.HasAnnotationKey("test")(m)).To(BeTrue())
   228  	})
   229  	t.Run("machine without specified annotation returns false", func(t *testing.T) {
   230  		g := NewWithT(t)
   231  		m := &clusterv1.Machine{}
   232  		g.Expect(collections.HasAnnotationKey("foo")(m)).To(BeFalse())
   233  	})
   234  }
   235  
   236  func TestInFailureDomain(t *testing.T) {
   237  	t.Run("nil machine returns false", func(t *testing.T) {
   238  		g := NewWithT(t)
   239  		g.Expect(collections.InFailureDomains(ptr.To("test"))(nil)).To(BeFalse())
   240  	})
   241  	t.Run("machine with given failure domain returns true", func(t *testing.T) {
   242  		g := NewWithT(t)
   243  		m := &clusterv1.Machine{Spec: clusterv1.MachineSpec{FailureDomain: ptr.To("test")}}
   244  		g.Expect(collections.InFailureDomains(ptr.To("test"))(m)).To(BeTrue())
   245  	})
   246  	t.Run("machine with a different failure domain returns false", func(t *testing.T) {
   247  		g := NewWithT(t)
   248  		m := &clusterv1.Machine{Spec: clusterv1.MachineSpec{FailureDomain: ptr.To("notTest")}}
   249  		g.Expect(collections.InFailureDomains(
   250  			ptr.To("test"),
   251  			ptr.To("test2"),
   252  			ptr.To("test3"),
   253  			nil,
   254  			ptr.To("foo"))(m)).To(BeFalse())
   255  	})
   256  	t.Run("machine without failure domain returns false", func(t *testing.T) {
   257  		g := NewWithT(t)
   258  		m := &clusterv1.Machine{}
   259  		g.Expect(collections.InFailureDomains(ptr.To("test"))(m)).To(BeFalse())
   260  	})
   261  	t.Run("machine without failure domain returns true, when nil used for failure domain", func(t *testing.T) {
   262  		g := NewWithT(t)
   263  		m := &clusterv1.Machine{}
   264  		g.Expect(collections.InFailureDomains(nil)(m)).To(BeTrue())
   265  	})
   266  	t.Run("machine with failure domain returns true, when one of multiple failure domains match", func(t *testing.T) {
   267  		g := NewWithT(t)
   268  		m := &clusterv1.Machine{Spec: clusterv1.MachineSpec{FailureDomain: ptr.To("test")}}
   269  		g.Expect(collections.InFailureDomains(ptr.To("foo"), ptr.To("test"))(m)).To(BeTrue())
   270  	})
   271  }
   272  
   273  func TestActiveMachinesInCluster(t *testing.T) {
   274  	t.Run("machine with deletion timestamp returns false", func(t *testing.T) {
   275  		g := NewWithT(t)
   276  		m := &clusterv1.Machine{}
   277  		now := metav1.Now()
   278  		m.SetDeletionTimestamp(&now)
   279  		g.Expect(collections.ActiveMachines(m)).To(BeFalse())
   280  	})
   281  	t.Run("machine with nil deletion timestamp returns true", func(t *testing.T) {
   282  		g := NewWithT(t)
   283  		m := &clusterv1.Machine{}
   284  		g.Expect(collections.ActiveMachines(m)).To(BeTrue())
   285  	})
   286  	t.Run("machine with zero deletion timestamp returns true", func(t *testing.T) {
   287  		g := NewWithT(t)
   288  		m := &clusterv1.Machine{}
   289  		zero := metav1.NewTime(time.Time{})
   290  		m.SetDeletionTimestamp(&zero)
   291  		g.Expect(collections.ActiveMachines(m)).To(BeTrue())
   292  	})
   293  }
   294  
   295  func TestMatchesKubernetesVersion(t *testing.T) {
   296  	t.Run("nil machine returns false", func(t *testing.T) {
   297  		g := NewWithT(t)
   298  		g.Expect(collections.MatchesKubernetesVersion("some_ver")(nil)).To(BeFalse())
   299  	})
   300  
   301  	t.Run("nil machine.Spec.Version returns false", func(t *testing.T) {
   302  		g := NewWithT(t)
   303  		machine := &clusterv1.Machine{
   304  			Spec: clusterv1.MachineSpec{
   305  				Version: nil,
   306  			},
   307  		}
   308  		g.Expect(collections.MatchesKubernetesVersion("some_ver")(machine)).To(BeFalse())
   309  	})
   310  
   311  	t.Run("machine.Spec.Version returns true if matches", func(t *testing.T) {
   312  		g := NewWithT(t)
   313  		kversion := "some_ver"
   314  		machine := &clusterv1.Machine{
   315  			Spec: clusterv1.MachineSpec{
   316  				Version: &kversion,
   317  			},
   318  		}
   319  		g.Expect(collections.MatchesKubernetesVersion("some_ver")(machine)).To(BeTrue())
   320  	})
   321  
   322  	t.Run("machine.Spec.Version returns false if does not match", func(t *testing.T) {
   323  		g := NewWithT(t)
   324  		kversion := "some_ver_2"
   325  		machine := &clusterv1.Machine{
   326  			Spec: clusterv1.MachineSpec{
   327  				Version: &kversion,
   328  			},
   329  		}
   330  		g.Expect(collections.MatchesKubernetesVersion("some_ver")(machine)).To(BeFalse())
   331  	})
   332  }
   333  
   334  func TestWithVersion(t *testing.T) {
   335  	t.Run("nil machine returns false", func(t *testing.T) {
   336  		g := NewWithT(t)
   337  		g.Expect(collections.WithVersion()(nil)).To(BeFalse())
   338  	})
   339  
   340  	t.Run("nil machine.Spec.Version returns false", func(t *testing.T) {
   341  		g := NewWithT(t)
   342  		machine := &clusterv1.Machine{
   343  			Spec: clusterv1.MachineSpec{
   344  				Version: nil,
   345  			},
   346  		}
   347  		g.Expect(collections.WithVersion()(machine)).To(BeFalse())
   348  	})
   349  
   350  	t.Run("empty machine.Spec.Version returns false", func(t *testing.T) {
   351  		g := NewWithT(t)
   352  		machine := &clusterv1.Machine{
   353  			Spec: clusterv1.MachineSpec{
   354  				Version: ptr.To(""),
   355  			},
   356  		}
   357  		g.Expect(collections.WithVersion()(machine)).To(BeFalse())
   358  	})
   359  
   360  	t.Run("invalid machine.Spec.Version returns false", func(t *testing.T) {
   361  		g := NewWithT(t)
   362  		machine := &clusterv1.Machine{
   363  			Spec: clusterv1.MachineSpec{
   364  				Version: ptr.To("1..20"),
   365  			},
   366  		}
   367  		g.Expect(collections.WithVersion()(machine)).To(BeFalse())
   368  	})
   369  
   370  	t.Run("valid machine.Spec.Version returns true", func(t *testing.T) {
   371  		g := NewWithT(t)
   372  		machine := &clusterv1.Machine{
   373  			Spec: clusterv1.MachineSpec{
   374  				Version: ptr.To("1.20"),
   375  			},
   376  		}
   377  		g.Expect(collections.WithVersion()(machine)).To(BeTrue())
   378  	})
   379  }
   380  
   381  func TestHealthyAPIServer(t *testing.T) {
   382  	t.Run("nil machine returns false", func(t *testing.T) {
   383  		g := NewWithT(t)
   384  		g.Expect(collections.HealthyAPIServer()(nil)).To(BeFalse())
   385  	})
   386  
   387  	t.Run("unhealthy machine returns false", func(t *testing.T) {
   388  		g := NewWithT(t)
   389  		machine := &clusterv1.Machine{}
   390  		g.Expect(collections.HealthyAPIServer()(machine)).To(BeFalse())
   391  	})
   392  
   393  	t.Run("healthy machine returns true", func(t *testing.T) {
   394  		g := NewWithT(t)
   395  		machine := &clusterv1.Machine{}
   396  		conditions.Set(machine, conditions.TrueCondition(controlplanev1.MachineAPIServerPodHealthyCondition))
   397  		g.Expect(collections.HealthyAPIServer()(machine)).To(BeTrue())
   398  	})
   399  }
   400  
   401  func TestGetFilteredMachinesForCluster(t *testing.T) {
   402  	g := NewWithT(t)
   403  
   404  	cluster := &clusterv1.Cluster{
   405  		ObjectMeta: metav1.ObjectMeta{
   406  			Namespace: "my-namespace",
   407  			Name:      "my-cluster",
   408  		},
   409  	}
   410  
   411  	c := fake.NewClientBuilder().
   412  		WithObjects(cluster,
   413  			testControlPlaneMachine("first-machine"),
   414  			testMachine("second-machine"),
   415  			testMachine("third-machine")).
   416  		Build()
   417  
   418  	machines, err := collections.GetFilteredMachinesForCluster(ctx, c, cluster)
   419  	g.Expect(err).ToNot(HaveOccurred())
   420  	g.Expect(machines).To(HaveLen(3))
   421  
   422  	// Test the ControlPlaneMachines works
   423  	machines, err = collections.GetFilteredMachinesForCluster(ctx, c, cluster, collections.ControlPlaneMachines("my-cluster"))
   424  	g.Expect(err).ToNot(HaveOccurred())
   425  	g.Expect(machines).To(HaveLen(1))
   426  
   427  	// Test that the filters use AND logic instead of OR logic
   428  	nameFilter := func(cluster *clusterv1.Machine) bool {
   429  		return cluster.Name == "first-machine"
   430  	}
   431  	machines, err = collections.GetFilteredMachinesForCluster(ctx, c, cluster, collections.ControlPlaneMachines("my-cluster"), nameFilter)
   432  	g.Expect(err).ToNot(HaveOccurred())
   433  	g.Expect(machines).To(HaveLen(1))
   434  }
   435  
   436  func TestHasNode(t *testing.T) {
   437  	t.Run("nil machine returns false", func(t *testing.T) {
   438  		g := NewWithT(t)
   439  		g.Expect(collections.HasNode()(nil)).To(BeFalse())
   440  	})
   441  
   442  	t.Run("machine without node returns false", func(t *testing.T) {
   443  		g := NewWithT(t)
   444  		machine := &clusterv1.Machine{}
   445  		g.Expect(collections.HasNode()(machine)).To(BeFalse())
   446  	})
   447  
   448  	t.Run("machine with node returns true", func(t *testing.T) {
   449  		g := NewWithT(t)
   450  		machine := &clusterv1.Machine{
   451  			Status: clusterv1.MachineStatus{NodeRef: &corev1.ObjectReference{Name: "foo"}},
   452  		}
   453  		g.Expect(collections.HasNode()(machine)).To(BeTrue())
   454  	})
   455  }
   456  
   457  func TestHasUnhealthyControlPlaneComponentCondition(t *testing.T) {
   458  	t.Run("nil machine returns false", func(t *testing.T) {
   459  		g := NewWithT(t)
   460  		g.Expect(collections.HasUnhealthyControlPlaneComponents(false)(nil)).To(BeFalse())
   461  	})
   462  
   463  	t.Run("machine without node returns false", func(t *testing.T) {
   464  		g := NewWithT(t)
   465  		machine := &clusterv1.Machine{}
   466  		g.Expect(collections.HasUnhealthyControlPlaneComponents(false)(machine)).To(BeFalse())
   467  	})
   468  
   469  	t.Run("machine with all healthy controlPlane component conditions returns false when the Etcd is not managed", func(t *testing.T) {
   470  		g := NewWithT(t)
   471  		machine := &clusterv1.Machine{}
   472  		machine.Status.NodeRef = &corev1.ObjectReference{
   473  			Name: "node1",
   474  		}
   475  		machine.Status.Conditions = clusterv1.Conditions{
   476  			*conditions.TrueCondition(controlplanev1.MachineAPIServerPodHealthyCondition),
   477  			*conditions.TrueCondition(controlplanev1.MachineControllerManagerPodHealthyCondition),
   478  			*conditions.TrueCondition(controlplanev1.MachineSchedulerPodHealthyCondition),
   479  		}
   480  		g.Expect(collections.HasUnhealthyControlPlaneComponents(false)(machine)).To(BeFalse())
   481  	})
   482  
   483  	t.Run("machine with unhealthy 'APIServerPodHealthy' condition returns true when the Etcd is not managed", func(t *testing.T) {
   484  		g := NewWithT(t)
   485  		machine := &clusterv1.Machine{}
   486  		machine.Status.NodeRef = &corev1.ObjectReference{
   487  			Name: "node1",
   488  		}
   489  		machine.Status.Conditions = clusterv1.Conditions{
   490  			*conditions.TrueCondition(controlplanev1.MachineControllerManagerPodHealthyCondition),
   491  			*conditions.TrueCondition(controlplanev1.MachineSchedulerPodHealthyCondition),
   492  			*conditions.FalseCondition(controlplanev1.MachineAPIServerPodHealthyCondition, "",
   493  				clusterv1.ConditionSeverityWarning, ""),
   494  		}
   495  		g.Expect(collections.HasUnhealthyControlPlaneComponents(false)(machine)).To(BeTrue())
   496  	})
   497  
   498  	t.Run("machine with unhealthy etcd component conditions returns false when Etcd is not managed", func(t *testing.T) {
   499  		g := NewWithT(t)
   500  		machine := &clusterv1.Machine{}
   501  		machine.Status.NodeRef = &corev1.ObjectReference{
   502  			Name: "node1",
   503  		}
   504  		machine.Status.Conditions = clusterv1.Conditions{
   505  			*conditions.TrueCondition(controlplanev1.MachineAPIServerPodHealthyCondition),
   506  			*conditions.TrueCondition(controlplanev1.MachineControllerManagerPodHealthyCondition),
   507  			*conditions.TrueCondition(controlplanev1.MachineSchedulerPodHealthyCondition),
   508  			*conditions.FalseCondition(controlplanev1.MachineEtcdPodHealthyCondition, "",
   509  				clusterv1.ConditionSeverityWarning, ""),
   510  			*conditions.FalseCondition(controlplanev1.MachineEtcdMemberHealthyCondition, "",
   511  				clusterv1.ConditionSeverityWarning, ""),
   512  		}
   513  		g.Expect(collections.HasUnhealthyControlPlaneComponents(false)(machine)).To(BeFalse())
   514  	})
   515  
   516  	t.Run("machine with unhealthy etcd conditions returns true when Etcd is managed", func(t *testing.T) {
   517  		g := NewWithT(t)
   518  		machine := &clusterv1.Machine{}
   519  		machine.Status.NodeRef = &corev1.ObjectReference{
   520  			Name: "node1",
   521  		}
   522  		machine.Status.Conditions = clusterv1.Conditions{
   523  			*conditions.TrueCondition(controlplanev1.MachineAPIServerPodHealthyCondition),
   524  			*conditions.TrueCondition(controlplanev1.MachineControllerManagerPodHealthyCondition),
   525  			*conditions.TrueCondition(controlplanev1.MachineSchedulerPodHealthyCondition),
   526  			*conditions.FalseCondition(controlplanev1.MachineEtcdPodHealthyCondition, "",
   527  				clusterv1.ConditionSeverityWarning, ""),
   528  			*conditions.FalseCondition(controlplanev1.MachineEtcdMemberHealthyCondition, "",
   529  				clusterv1.ConditionSeverityWarning, ""),
   530  		}
   531  		g.Expect(collections.HasUnhealthyControlPlaneComponents(true)(machine)).To(BeTrue())
   532  	})
   533  
   534  	t.Run("machine with all healthy controlPlane and the Etcd component conditions returns false when Etcd is managed", func(t *testing.T) {
   535  		g := NewWithT(t)
   536  		machine := &clusterv1.Machine{}
   537  		machine.Status.NodeRef = &corev1.ObjectReference{
   538  			Name: "node1",
   539  		}
   540  		machine.Status.Conditions = clusterv1.Conditions{
   541  			*conditions.TrueCondition(controlplanev1.MachineAPIServerPodHealthyCondition),
   542  			*conditions.TrueCondition(controlplanev1.MachineControllerManagerPodHealthyCondition),
   543  			*conditions.TrueCondition(controlplanev1.MachineSchedulerPodHealthyCondition),
   544  			*conditions.TrueCondition(controlplanev1.MachineEtcdPodHealthyCondition),
   545  			*conditions.TrueCondition(controlplanev1.MachineEtcdMemberHealthyCondition),
   546  		}
   547  		g.Expect(collections.HasUnhealthyControlPlaneComponents(true)(machine)).To(BeFalse())
   548  	})
   549  }
   550  
   551  func testControlPlaneMachine(name string) *clusterv1.Machine {
   552  	owned := true
   553  	ownedRef := []metav1.OwnerReference{
   554  		{
   555  			Kind:       "KubeadmControlPlane",
   556  			Name:       "my-control-plane",
   557  			Controller: &owned,
   558  		},
   559  	}
   560  	controlPlaneMachine := testMachine(name)
   561  	controlPlaneMachine.ObjectMeta.Labels[clusterv1.MachineControlPlaneLabel] = ""
   562  	controlPlaneMachine.OwnerReferences = ownedRef
   563  
   564  	return controlPlaneMachine
   565  }
   566  
   567  func testMachine(name string) *clusterv1.Machine {
   568  	return &clusterv1.Machine{
   569  		TypeMeta: metav1.TypeMeta{},
   570  		ObjectMeta: metav1.ObjectMeta{
   571  			Name:      name,
   572  			Namespace: "my-namespace",
   573  			Labels: map[string]string{
   574  				clusterv1.ClusterNameLabel: "my-cluster",
   575  			},
   576  		},
   577  	}
   578  }